Send audience members

  • This quickstart guide familiarizes users with the Data Manager API by outlining steps to prepare a destination, format and prepare audience data, build an ingestion request, send the request, and understand responses.

  • Audience data, such as email addresses, must be formatted by removing whitespace, converting to lowercase, hashing with SHA-256, and encoding using hex or Base64.

  • Requests require a combination of destination information and formatted audience member data in the request body.

  • Successful requests return a requestId for tracking, while failures provide error details.

  • You can send audience members to multiple destinations in a single request by using destination references within the request body.

You can work through this quickstart to get familiar with the Data Manager API. Choose the version of the quickstart you want to see.

  • Select Advertiser if you're using credentials for a Google Account that is a user in the advertiser accounts you want to manage.
  • Select Data Partner if you're using credentials for a Google Account that is a user in a data partner account, and you want to manage advertiser accounts that have a partner link to the data partner account.

In this quickstart, you complete the following steps:

  1. Prepare a Destination to receive audience data.
  2. Prepare audience data to send.
  3. Build an IngestionService request for audience members.
  4. Send the request with the Google APIs Explorer.
  5. Understand success and failure responses.

Prepare a destination

Before you can send data, you need to prepare the destination to send the data to. Here's a sample Destination for you to use. Check out Configure destinations for examples of destinations for different scenarios.

   
 { 
  
 "operatingAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " OPERATING_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " OPERATING_ACCOUNT_ID 
" 
  
 }, 
  
 "loginAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " LOGIN_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " LOGIN_ACCOUNT_ID 
" 
  
 }, 
  
 "productDestinationId" 
 : 
  
 " AUDIENCE_ID 
" 
  
 } 
 
  • Set the operatingAccount to the account type and ID of the account that will receive the audience data.
  • If your OAuth credentials are for a user with access to a Google Ads manager account , that has the operatingAccount as one of its subaccounts, set the loginAccount to the account type and ID of the manager account.
  • If the OAuth credentials are for a user with direct access to the operatingAccount , you don't need to set loginAccount .

Prepare audience data

Consider the following sample data in a comma-separated file. Each line in the file corresponds to one member of the audience, and each member has up to three email addresses.

 #,email_1,email_2,email_3
1,dana@example.com,DanaM@example.com,
2,ALEXJ@example.com, AlexJ@cymbalgroup.com,alexj@altostrat.com
3,quinn@CYMBALGROUP.com,baklavainthebalkans@gmail.com  ,
4,rosario@example.org,cloudySanFrancisco@GMAIL.com, 

Email addresses have the following formatting and hashing requirements:

  1. Remove all leading, trailing, and intermediate whitespace.
  2. Convert the email address to lowercase.
  3. Hash the email address using the SHA-256 algorithm .
  4. Encode the hash bytes using hexadecimal (hex) or Base64 encoding . The examples in this guide use hex encoding.

Here's the formatted data:

 #,email_1,email_2,email_3
1,dana@example.com,danam@example.com,
2,alexj@example.com,alexj@cymbalgroup.com,alexj@altostrat.com
3,quinn@cymbalgroup.com,baklavainthebalkans@gmail.com,
4,rosario@example.org,cloudysanfrancisco@gmail.com, 

And here's the data after being hashed and encoded:

 #,email_1,email_2,email_3
1,07e2f1394b0ea80e2adca010ea8318df697001a005ba7452720edda4b0ce57b3,1df6b43bc68dd38eca94e6a65b4f466ae537b796c81a526918b40ac4a7b906c7
2,2ef46c4214c3fc1b277a2d976d55194e12b899aa50d721f28da858c7689756e3,54e410b14fa652a4b49b43aff6eaf92ad680d4d1e5e62ed71b86cd3188385a51,e8bd3f8da6f5af73bec1ab3fbf7beb47482c4766dfdfc94e6bd89e359c139478
3,05bb62526f091b45d20e243d194766cca8869137421047dc53fa4876d111a6f0,f1fcde379f31f4d446b76ee8f34860eca2288adc6b6d6c0fdc56d9eee75a2fa5
4,83a834cc5327bc4dee7c5408988040dc5813c7662611cd93b707aff72bf7d33f,223ebda6f6889b1494551ba902d9d381daf2f642bae055888e96343d53e9f9c4 

Here's a sample AudienceMember for the formatted, hashed, and encoded email addresses of dana@example.com and danam@example.com from the first row of the input data:

  { 
  
 "userData" 
 : 
  
 { 
  
 "userIdentifiers" 
 : 
  
 [ 
  
 { 
  
 "emailAddress" 
 : 
  
 "07e2f1394b0ea80e2adca010ea8318df697001a005ba7452720edda4b0ce57b3" 
  
 }, 
  
 { 
  
 "emailAddress" 
 : 
  
 "1df6b43bc68dd38eca94e6a65b4f466ae537b796c81a526918b40ac4a7b906c7" 
  
 } 
  
 ] 
  
 } 
 } 
 

Build the request body

To build the request body, combine the destinations and audienceMembers , set the encoding field, and add any other request fields you want to include such as validateOnly and consent .

If sending audience members for Customer Match, set termsOfService to indicate whether the user has accepted the Customer Match terms of service .

The examples in this guide don't use encryption, but you can follow the instructions in Encrypt user data to add encryption to your process.

Send the request

Here are the steps to try a request from your browser:

  1. Select the RESTtab and click Open in API Explorerto open the API Explorer in a new tab or window.
  2. In the request body in the API Explorer, replace each string beginning with REPLACE_WITH , such as REPLACE_WITH_OPERATING_ACCOUNT_TYPE , with the relevant value.
  3. Click Executeat the bottom of the API Explorer page and complete the authorization prompts to send the request.
  4. Set validateOnly to true to validate the request without applying the changes. When you're ready to apply the changes, set validateOnly to false .

If you installed a client library , select the tab for your chosen programming language to see a complete code sample of how to construct and send a request.

REST

 { 
  
 "destinations" 
 : 
  
 [ 
  
 { 
  
 "operatingAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " OPERATING_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " OPERATING_ACCOUNT_ID 
" 
  
 }, 
  
 "loginAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " LOGIN_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " LOGIN_ACCOUNT_ID 
" 
  
 }, 
  
 "productDestinationId" 
 : 
  
 " AUDIENCE_ID 
" 
  
 } 
  
 ], 
  
 "audienceMembers" 
 : 
  
 [ 
  
 { 
  
 "userData" 
 : 
  
 { 
  
 "userIdentifiers" 
 : 
  
 [ 
  
 { 
  
 "emailAddress" 
 : 
  
 "07e2f1394b0ea80e2adca010ea8318df697001a005ba7452720edda4b0ce57b3" 
  
 }, 
  
 { 
  
 "emailAddress" 
 : 
  
 "1df6b43bc68dd38eca94e6a65b4f466ae537b796c81a526918b40ac4a7b906c7" 
  
 } 
  
 ] 
  
 } 
  
 }, 
  
 { 
  
 "userData" 
 : 
  
 { 
  
 "userIdentifiers" 
 : 
  
 [ 
  
 { 
  
 "emailAddress" 
 : 
  
 "2ef46c4214c3fc1b277a2d976d55194e12b899aa50d721f28da858c7689756e3" 
  
 }, 
  
 { 
  
 "emailAddress" 
 : 
  
 "54e410b14fa652a4b49b43aff6eaf92ad680d4d1e5e62ed71b86cd3188385a51" 
  
 }, 
  
 { 
  
 "emailAddress" 
 : 
  
 "e8bd3f8da6f5af73bec1ab3fbf7beb47482c4766dfdfc94e6bd89e359c139478" 
  
 } 
  
 ] 
  
 } 
  
 }, 
  
 { 
  
 "userData" 
 : 
  
 { 
  
 "userIdentifiers" 
 : 
  
 [ 
  
 { 
  
 "emailAddress" 
 : 
  
 "05bb62526f091b45d20e243d194766cca8869137421047dc53fa4876d111a6f0" 
  
 }, 
  
 { 
  
 "emailAddress" 
 : 
  
 "f1fcde379f31f4d446b76ee8f34860eca2288adc6b6d6c0fdc56d9eee75a2fa5" 
  
 } 
  
 ] 
  
 } 
  
 }, 
  
 { 
  
 "userData" 
 : 
  
 { 
  
 "userIdentifiers" 
 : 
  
 [ 
  
 { 
  
 "emailAddress" 
 : 
  
 "83a834cc5327bc4dee7c5408988040dc5813c7662611cd93b707aff72bf7d33f" 
  
 }, 
  
 { 
  
 "emailAddress" 
 : 
  
 "223ebda6f6889b1494551ba902d9d381daf2f642bae055888e96343d53e9f9c4" 
  
 } 
  
 ] 
  
 } 
  
 } 
  
 ], 
  
 "consent" 
 : 
  
 { 
  
 "adUserData" 
 : 
  
 "CONSENT_GRANTED" 
 , 
  
 "adPersonalization" 
 : 
  
 "CONSENT_GRANTED" 
  
 }, 
  
 "encoding" 
 : 
  
 "HEX" 
 , 
  
 "termsOfService" 
 : 
  
 { 
  
 "customerMatchTermsOfServiceStatus" 
 : 
  
 "ACCEPTED" 
  
 }, 
  
 "validateOnly" 
 : 
  
 true 
 } 

.NET

 // 
  
 Copyright 
  
 2025 
  
 Google 
  
 LLC 
 // 
 // 
  
 Licensed 
  
 under 
  
 the 
  
 Apache 
  
 License 
 , 
  
 Version 
  
 2.0 
  
 ( 
 the 
  
 "License" 
 ); 
 // 
  
 you 
  
 may 
  
 not 
  
 use 
  
 this 
  
 file 
  
 except 
  
 in 
  
 compliance 
  
 with 
  
 the 
  
 License 
 . 
 // 
  
 You 
  
 may 
  
 obtain 
  
 a 
  
 copy 
  
 of 
  
 the 
  
 License 
  
 at 
 // 
 // 
  
 http 
 : 
 // 
 www 
 . 
 apache 
 . 
 org 
 / 
 licenses 
 / 
 LICENSE 
 - 
 2.0 
 // 
 // 
  
 Unless 
  
 required 
  
 by 
  
 applicable 
  
 law 
  
 or 
  
 agreed 
  
 to 
  
 in 
  
 writing 
 , 
  
 software 
 // 
  
 distributed 
  
 under 
  
 the 
  
 License 
  
 is 
  
 distributed 
  
 on 
  
 an 
  
 "AS IS" 
  
 BASIS 
 , 
 // 
  
 WITHOUT 
  
 WARRANTIES 
  
 OR 
  
 CONDITIONS 
  
 OF 
  
 ANY 
  
 KIND 
 , 
  
 either 
  
 express 
  
 or 
  
 implied 
 . 
 // 
  
 See 
  
 the 
  
 License 
  
 for 
  
 the 
  
 specific 
  
 language 
  
 governing 
  
 permissions 
  
 and 
 // 
  
 limitations 
  
 under 
  
 the 
  
 License 
 . 
 using 
  
 CommandLine 
 ; 
 using 
  
 Google 
 . 
 Ads 
 . 
 DataManager 
 . 
 Util 
 ; 
 using 
  
 Google 
 . 
 Ads 
 . 
 DataManager 
 . 
 V1 
 ; 
 using 
  
 static 
  
 Google 
 . 
 Ads 
 . 
 DataManager 
 . 
 V1 
 . 
 ProductAccount 
 . 
 Types 
 ; 
 namespace 
  
 Google 
 . 
 Ads 
 . 
 DataManager 
 . 
 Samples 
 { 
  
 // 
  
< summary 
>  
 // 
  
 Sends 
  
 a 
  
< see 
  
 cref 
 = 
 "IngestAudienceMembersRequest" 
  
 / 
>  
 without 
  
 using 
  
 encryption 
 . 
  
 // 
  
 // 
  
 User 
  
 data 
  
 is 
  
 read 
  
 from 
  
 a 
  
 data 
  
 file 
 . 
  
 See 
  
 the 
  
< c>audience_members_1 
 . 
 csv 
< / 
 c 
>  
 file 
  
 in 
  
 the 
  
 // 
  
< c>sampledata 
< / 
 c 
>  
 directory 
  
 for 
  
 an 
  
 example 
 . 
  
 // 
  
< / 
 summary 
>  
 public 
  
 class 
  
 IngestAudienceMembers 
  
 { 
  
 private 
  
 static 
  
 readonly 
  
 int 
  
 MaxMembersPerRequest 
  
 = 
  
 10 
 _000 
 ; 
  
 [ 
 Verb( 
 "ingest-audience-members", 
 HelpText = "Sends an IngestAudienceMembersRequest without using encryption." 
 ) 
 ] 
  
 public 
  
 class 
  
 Options 
  
 { 
  
 [ 
 Option( 
 "operatingAccountType", 
 Required = true, 
 HelpText = "Account type of the operating account" 
 ) 
 ] 
  
 public 
  
 AccountType 
  
 OperatingAccountType 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 [ 
 Option( 
 "operatingAccountId", 
 Required = true, 
 HelpText = "ID of the operating account" 
 ) 
 ] 
  
 public 
  
 string 
  
 OperatingAccountId 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 = 
  
 null 
 ! 
 ; 
  
 [ 
 Option( 
 "loginAccountType", 
 Required = false, 
 HelpText = "Account type of the login account" 
 ) 
 ] 
  
 public 
  
 AccountType 
 ? 
  
 LoginAccountType 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 [ 
 Option("loginAccountId", Required = false, HelpText = "ID of the login account") 
 ] 
  
 public 
  
 string 
 ? 
  
 LoginAccountId 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 [ 
 Option( 
 "linkedAccountType", 
 Required = false, 
 HelpText = "Account type of the linked account" 
 ) 
 ] 
  
 public 
  
 AccountType 
 ? 
  
 LinkedAccountType 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 [ 
 Option("linkedAccountId", Required = false, HelpText = "ID of the linked account") 
 ] 
  
 public 
  
 string 
 ? 
  
 LinkedAccountId 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 [ 
 Option("audienceId", Required = true, HelpText = "ID of the audience") 
 ] 
  
 public 
  
 string 
  
 AudienceId 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 = 
  
 null 
 ! 
 ; 
  
 [ 
 Option( 
 "csvFile", 
 Required = true, 
 HelpText = "Comma-separated file containing user data to ingest" 
 ) 
 ] 
  
 public 
  
 string 
  
 CsvFile 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 = 
  
 null 
 ! 
 ; 
  
 [ 
 Option( 
 "validateOnly", 
 Default = true, 
 HelpText = "Whether to enable validateOnly on the request" 
 ) 
 ] 
  
 public 
  
 bool 
  
 ValidateOnly 
  
 { 
  
 get 
 ; 
  
 set 
 ; 
  
 } 
  
 } 
  
 public 
  
 void 
  
 Run 
 ( 
 Options 
  
 options 
 ) 
  
 { 
  
 RunExample 
 ( 
  
 options 
 . 
 OperatingAccountType 
 , 
  
 options 
 . 
 OperatingAccountId 
 , 
  
 options 
 . 
 LoginAccountType 
 , 
  
 options 
 . 
 LoginAccountId 
 , 
  
 options 
 . 
 LinkedAccountType 
 , 
  
 options 
 . 
 LinkedAccountId 
 , 
  
 options 
 . 
 AudienceId 
 , 
  
 options 
 . 
 CsvFile 
 , 
  
 options 
 . 
 ValidateOnly 
  
 ); 
  
 } 
  
 // 
  
 Runs 
  
 the 
  
 example 
 . 
  
 private 
  
 void 
  
 RunExample 
 ( 
  
 AccountType 
  
 operatingAccountType 
 , 
  
 string 
  
 operatingAccountId 
 , 
  
 AccountType 
 ? 
  
 loginAccountType 
 , 
  
 string 
 ? 
  
 loginAccountId 
 , 
  
 AccountType 
 ? 
  
 linkedAccountType 
 , 
  
 string 
 ? 
  
 linkedAccountId 
 , 
  
 string 
  
 audienceId 
 , 
  
 string 
  
 csvFile 
 , 
  
 bool 
  
 validateOnly 
  
 ) 
  
 { 
  
 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" 
  
 ); 
  
 } 
  
 // 
  
 Reads 
  
 the 
  
 audience 
  
 members 
  
 from 
  
 the 
  
 CSV 
  
 file 
 . 
  
 // 
  
 Each 
  
 row 
  
 of 
  
 the 
  
 CSV 
  
 file 
  
 should 
  
 be 
  
 a 
  
 single 
  
 audience 
  
 member 
 . 
  
 // 
  
 The 
  
 first 
  
 column 
  
 of 
  
 each 
  
 row 
  
 should 
  
 be 
  
 the 
  
 email 
  
 address 
 . 
  
 // 
  
 The 
  
 second 
  
 column 
  
 of 
  
 each 
  
 row 
  
 should 
  
 be 
  
 the 
  
 phone 
  
 number 
 . 
  
 List<Member> 
  
 memberList 
  
 = 
  
 ReadMemberDataFile 
 ( 
 csvFile 
 ); 
  
 // 
  
 Creates 
  
 a 
  
 factory 
  
 that 
  
 will 
  
 be 
  
 used 
  
 to 
  
 generate 
  
 the 
  
 appropriate 
  
 data 
  
 manager 
 . 
  
 var 
  
 userDataFormatter 
  
 = 
  
 new 
  
 UserDataFormatter 
 (); 
  
 var 
  
 audienceMembers 
  
 = 
  
 new 
  
 List<AudienceMember> 
 (); 
  
 // 
  
 Processes 
  
 each 
  
 batch 
  
 of 
  
 audience 
  
 members 
 . 
  
 foreach 
  
 ( 
 var 
  
 member 
  
 in 
  
 memberList 
 ) 
  
 { 
  
 var 
  
 userDataBuilder 
  
 = 
  
 new 
  
 UserData 
 (); 
  
 // 
  
 Adds 
  
 a 
  
 UserIdentifier 
  
 for 
  
 each 
  
 valid 
  
 email 
  
 address 
  
 for 
  
 the 
  
 member 
 . 
  
 foreach 
  
 ( 
 var 
  
 email 
  
 in 
  
 member 
 . 
 EmailAddresses 
 ) 
  
 { 
  
 try 
  
 { 
  
 string 
  
 processedEmail 
  
 = 
  
 userDataFormatter 
 . 
 ProcessEmailAddress 
 ( 
  
 email 
 , 
  
 UserDataFormatter 
 . 
 Encoding 
 . 
 Hex 
  
 ); 
  
 // 
  
 Sets 
  
 the 
  
 email 
  
 address 
  
 identifier 
  
 to 
  
 the 
  
 encoded 
  
 hash 
 . 
  
 userDataBuilder 
 . 
 UserIdentifiers 
 . 
 Add 
 ( 
  
 new 
  
 UserIdentifier 
  
 { 
  
 EmailAddress 
  
 = 
  
 processedEmail 
  
 } 
  
 ); 
  
 } 
  
 catch 
  
 ( 
 ArgumentException 
 ) 
  
 { 
  
 // 
  
 Skips 
  
 invalid 
  
 input 
 . 
  
 continue 
 ; 
  
 } 
  
 } 
  
 // 
  
 Adds 
  
 a 
  
 UserIdentifier 
  
 for 
  
 each 
  
 valid 
  
 phone 
  
 number 
  
 for 
  
 the 
  
 member 
 . 
  
 foreach 
  
 ( 
 var 
  
 phoneNumber 
  
 in 
  
 member 
 . 
 PhoneNumbers 
 ) 
  
 { 
  
 try 
  
 { 
  
 string 
  
 processedPhoneNumber 
  
 = 
  
 userDataFormatter 
 . 
 ProcessPhoneNumber 
 ( 
  
 phoneNumber 
 , 
  
 UserDataFormatter 
 . 
 Encoding 
 . 
 Hex 
  
 ); 
  
 // 
  
 Sets 
  
 the 
  
 phone 
  
 number 
  
 identifier 
  
 to 
  
 the 
  
 encoded 
  
 hash 
 . 
  
 userDataBuilder 
 . 
 UserIdentifiers 
 . 
 Add 
 ( 
  
 new 
  
 UserIdentifier 
  
 { 
  
 PhoneNumber 
  
 = 
  
 processedPhoneNumber 
  
 } 
  
 ); 
  
 } 
  
 catch 
  
 ( 
 ArgumentException 
 ) 
  
 { 
  
 // 
  
 Skips 
  
 invalid 
  
 input 
 . 
  
 continue 
 ; 
  
 } 
  
 } 
  
 if 
  
 ( 
 userDataBuilder 
 . 
 UserIdentifiers 
 . 
 Any 
 ()) 
  
 { 
  
 audienceMembers 
 . 
 Add 
 ( 
 new 
  
 AudienceMember 
  
 { 
  
 UserData 
  
 = 
  
 userDataBuilder 
  
 } 
 ); 
  
 } 
  
 } 
  
 // 
  
 Builds 
  
 the 
  
 Destination 
  
 for 
  
 the 
  
 request 
 . 
  
 var 
  
 destinationBuilder 
  
 = 
  
 new 
  
 Destination 
  
 { 
  
 // 
  
 The 
  
 destination 
  
 account 
  
 for 
  
 the 
  
 data 
 . 
  
 OperatingAccount 
  
 = 
  
 new 
  
 ProductAccount 
  
 { 
  
 AccountType 
  
 = 
  
 operatingAccountType 
 , 
  
 AccountId 
  
 = 
  
 operatingAccountId 
 , 
  
 } 
 , 
  
 // 
  
 The 
  
 ID 
  
 of 
  
 the 
  
 user 
  
 list 
  
 that 
  
 is 
  
 being 
  
 updated 
 . 
  
 ProductDestinationId 
  
 = 
  
 audienceId 
 , 
  
 } 
 ; 
  
 if 
  
 ( 
 loginAccountType 
 . 
 HasValue 
 && 
 loginAccountId 
  
 != 
  
 null 
 ) 
  
 { 
  
 destinationBuilder 
 . 
 LoginAccount 
  
 = 
  
 new 
  
 ProductAccount 
  
 { 
  
 AccountType 
  
 = 
  
 loginAccountType 
 . 
 Value 
 , 
  
 AccountId 
  
 = 
  
 loginAccountId 
 , 
  
 } 
 ; 
  
 } 
  
 if 
  
 ( 
 linkedAccountType 
 . 
 HasValue 
 && 
 linkedAccountId 
  
 != 
  
 null 
 ) 
  
 { 
  
 destinationBuilder 
 . 
 LinkedAccount 
  
 = 
  
 new 
  
 ProductAccount 
  
 { 
  
 AccountType 
  
 = 
  
 linkedAccountType 
 . 
 Value 
 , 
  
 AccountId 
  
 = 
  
 linkedAccountId 
 , 
  
 } 
 ; 
  
 } 
  
 IngestionServiceClient 
  
 ingestionServiceClient 
  
 = 
  
 IngestionServiceClient 
 . 
 Create 
 (); 
  
 int 
  
 requestCount 
  
 = 
  
 0 
 ; 
  
 // 
  
 Batches 
  
 requests 
  
 to 
  
 send 
  
 up 
  
 to 
  
 the 
  
 maximum 
  
 number 
  
 of 
  
 audience 
  
 members 
  
 per 
  
 request 
 . 
  
 for 
  
 ( 
 var 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 audienceMembers 
 . 
 Count 
 ; 
  
 i 
  
 += 
  
 MaxMembersPerRequest 
 ) 
  
 { 
  
 IEnumerable<AudienceMember> 
  
 membersBatch 
  
 = 
  
 audienceMembers 
  
 . 
 Skip 
 ( 
 i 
 ) 
  
 . 
 Take 
 ( 
 MaxMembersPerRequest 
 ); 
  
 requestCount 
 ++ 
 ; 
  
 // 
  
 Builds 
  
 the 
  
 request 
 . 
  
 var 
  
 request 
  
 = 
  
 new 
  
 IngestAudienceMembersRequest 
  
 { 
  
 Destinations 
  
 = 
  
 { 
  
 destinationBuilder 
  
 } 
 , 
  
 // 
  
 Adds 
  
 members 
  
 from 
  
 the 
  
 current 
  
 batch 
 . 
  
 AudienceMembers 
  
 = 
  
 { 
  
 membersBatch 
  
 } 
 , 
  
 Consent 
  
 = 
  
 new 
  
 Consent 
  
 { 
  
 AdPersonalization 
  
 = 
  
 ConsentStatus 
 . 
 ConsentGranted 
 , 
  
 AdUserData 
  
 = 
  
 ConsentStatus 
 . 
 ConsentGranted 
 , 
  
 } 
 , 
  
 // 
  
 Sets 
  
 validate_only 
 . 
  
 If 
  
 true 
 , 
  
 then 
  
 the 
  
 Data 
  
 Manager 
  
 API 
  
 only 
  
 validates 
  
 the 
  
 // 
  
 request 
  
 but 
  
 doesn 
 't apply changes. 
 ValidateOnly = validateOnly, 
 Encoding = V1.Encoding.Hex, 
 TermsOfService = new TermsOfService 
 { 
 CustomerMatchTermsOfServiceStatus = TermsOfServiceStatus.Accepted, 
 }, 
 }; 
 // Sends the data to the Data Manager API. 
 IngestAudienceMembersResponse response = 
 ingestionServiceClient.IngestAudienceMembers(request); 
 Console.WriteLine($"Response for request #{requestCount}:\n{response}"); 
 } 
 Console.WriteLine($"# of requests sent: {requestCount}"); 
 } 
 private class Member 
 { 
 public List<string> EmailAddresses { get; } = new List<string>(); 
 public List<string> PhoneNumbers { get; } = new List<string>(); 
 } 
 private List<Member> ReadMemberDataFile(string dataFile) 
 { 
 var members = new List<Member>(); 
 using (var reader = new StreamReader(dataFile)) 
 { 
 string? line; 
 int lineNumber = 0; 
 while ((line = reader.ReadLine()) != null) 
 { 
 lineNumber++; 
 if (line.StartsWith("#")) 
 // Skips comment row. 
 continue; 
 // Expected format: 
 // email_1,email_2,email_3,phone_1,phone_2,phone_3 
 string[] columns = line.Split(' 
 , 
 ' 
 ); 
  
 if 
  
 ( 
 columns 
 [ 
 0 
 ] 
  
 == 
  
 "email_1" 
 ) 
  
 // 
  
 Skips 
  
 header 
  
 row 
 . 
  
 continue 
 ; 
  
 var 
  
 member 
  
 = 
  
 new 
  
 Member 
 (); 
  
 for 
  
 ( 
 int 
  
 col 
  
 = 
  
 0 
 ; 
  
 col 
 < 
 columns 
 . 
 Length 
 ; 
  
 col 
 ++ 
 ) 
  
 { 
  
 if 
  
 ( 
 string 
 . 
 IsNullOrWhiteSpace 
 ( 
 columns 
 [ 
 col 
 ] 
 )) 
  
 { 
  
 continue 
 ; 
  
 } 
  
 if 
  
 ( 
 col 
 < 
 3 
 ) 
  
 { 
  
 member 
 . 
 EmailAddresses 
 . 
 Add 
 ( 
 columns 
 [ 
 col 
 ] 
 ); 
  
 } 
  
 else 
  
 if 
  
 ( 
 col 
 < 
 6 
 ) 
  
 { 
  
 member 
 . 
 PhoneNumbers 
 . 
 Add 
 ( 
 columns 
 [ 
 col 
 ] 
 ); 
  
 } 
  
 else 
  
 { 
  
 Console 
 . 
 WriteLine 
 ( 
 $ 
 "Ignoring column index {col} in line #{lineNumber}" 
 ); 
  
 } 
  
 } 
  
 if 
  
 ( 
 ! 
 member 
 . 
 EmailAddresses 
 . 
 Any 
 () 
 && 
 ! 
 member 
 . 
 PhoneNumbers 
 . 
 Any 
 ()) 
  
 { 
  
 // 
  
 Skips 
  
 the 
  
 row 
  
 since 
  
 it 
  
 contains 
  
 no 
  
 user 
  
 data 
 . 
  
 Console 
 . 
 WriteLine 
 ( 
 $ 
 "Ignoring line {lineNumber}. No data." 
 ); 
  
 } 
  
 else 
  
 { 
  
 // 
  
 Adds 
  
 the 
  
 parsed 
  
 user 
  
 data 
  
 to 
  
 the 
  
 list 
 . 
  
 members 
 . 
 Add 
 ( 
 member 
 ); 
  
 } 
  
 } 
  
 } 
  
 return 
  
 members 
 ; 
  
 } 
  
 } 
 } 
  

Java

 // Copyright 2025 Google LLC 
 // 
 // Licensed under the Apache License, Version 2.0 (the "License"); 
 // you may not use this file except in compliance with the License. 
 // You may obtain a copy of the License at 
 // 
 //     http://www.apache.org/licenses/LICENSE-2.0 
 // 
 // Unless required by applicable law or agreed to in writing, software 
 // distributed under the License is distributed on an "AS IS" BASIS, 
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 // See the License for the specific language governing permissions and 
 // limitations under the License. 
 package 
  
 com.google.ads.datamanager.samples 
 ; 
 import 
  
 com.beust.jcommander.Parameter 
 ; 
 import 
  
 com.google.ads.datamanager.samples.common.BaseParamsConfig 
 ; 
 import 
  
 com.google.ads.datamanager.util.Encrypter 
 ; 
 import 
  
 com.google.ads.datamanager.util.UserDataFormatter 
 ; 
 import 
  
 com.google.ads.datamanager.util.UserDataFormatter.Encoding 
 ; 
 import 
  
 com.google.ads.datamanager.v1.AudienceMember 
 ; 
 import 
  
 com.google.ads.datamanager.v1.Consent 
 ; 
 import 
  
 com.google.ads.datamanager.v1.ConsentStatus 
 ; 
 import 
  
 com.google.ads.datamanager.v1.Destination 
 ; 
 import 
  
 com.google.ads.datamanager.v1.EncryptionInfo 
 ; 
 import 
  
 com.google.ads.datamanager.v1.GcpWrappedKeyInfo 
 ; 
 import 
  
 com.google.ads.datamanager.v1.GcpWrappedKeyInfo.KeyType 
 ; 
 import 
  
 com.google.ads.datamanager.v1.IngestAudienceMembersRequest 
 ; 
 import 
  
 com.google.ads.datamanager.v1.IngestAudienceMembersResponse 
 ; 
 import 
  
 com.google.ads.datamanager.v1.IngestionServiceClient 
 ; 
 import 
  
 com.google.ads.datamanager.v1.ProductAccount 
 ; 
 import 
  
 com.google.ads.datamanager.v1.ProductAccount.AccountType 
 ; 
 import 
  
 com.google.ads.datamanager.v1.TermsOfService 
 ; 
 import 
  
 com.google.ads.datamanager.v1.TermsOfServiceStatus 
 ; 
 import 
  
 com.google.ads.datamanager.v1.UserData 
 ; 
 import 
  
 com.google.ads.datamanager.v1.UserIdentifier 
 ; 
 import 
  
 com.google.common.collect.Lists 
 ; 
 import 
  
 java.io.BufferedReader 
 ; 
 import 
  
 java.io.FileReader 
 ; 
 import 
  
 java.io.IOException 
 ; 
 import 
  
 java.security.GeneralSecurityException 
 ; 
 import 
  
 java.util.ArrayList 
 ; 
 import 
  
 java.util.List 
 ; 
 import 
  
 java.util.logging.Level 
 ; 
 import 
  
 java.util.logging.Logger 
 ; 
 /** 
 * Sends an {@link IngestAudienceMembersRequest} with the option to use encryption. 
 * 
 * <p>User data is read from a data file. See the {@code audience_members_1.csv} file in the {@code 
 * resources/sampledata} directory for a sample file. 
 */ 
 public 
  
 class 
 IngestAudienceMembers 
  
 { 
  
 private 
  
 static 
  
 final 
  
 Logger 
  
 LOGGER 
  
 = 
  
 Logger 
 . 
 getLogger 
 ( 
 IngestAudienceMembers 
 . 
 class 
 . 
 getName 
 ()); 
  
 /** The maximum number of audience members allowed per request. */ 
  
 private 
  
 static 
  
 final 
  
 int 
  
 MAX_MEMBERS_PER_REQUEST 
  
 = 
  
 10_000 
 ; 
  
 private 
  
 static 
  
 final 
  
 class 
 ParamsConfig 
  
 extends 
  
 BaseParamsConfig<ParamsConfig> 
  
 { 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--operatingAccountType" 
 , 
  
 required 
  
 = 
  
 true 
 , 
  
 description 
  
 = 
  
 "Account type of the operating account" 
 ) 
  
 AccountType 
  
 operatingAccountType 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--operatingAccountId" 
 , 
  
 required 
  
 = 
  
 true 
 , 
  
 description 
  
 = 
  
 "ID of the operating account" 
 ) 
  
 String 
  
 operatingAccountId 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--loginAccountType" 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 description 
  
 = 
  
 "Account type of the login account" 
 ) 
  
 AccountType 
  
 loginAccountType 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--loginAccountId" 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 description 
  
 = 
  
 "ID of the login account" 
 ) 
  
 String 
  
 loginAccountId 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--linkedAccountType" 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 description 
  
 = 
  
 "Account type of the linked account" 
 ) 
  
 AccountType 
  
 linkedAccountType 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--linkedAccountId" 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 description 
  
 = 
  
 "ID of the linked account" 
 ) 
  
 String 
  
 linkedAccountId 
 ; 
  
 @Parameter 
 ( 
 names 
  
 = 
  
 "--audienceId" 
 , 
  
 required 
  
 = 
  
 true 
 , 
  
 description 
  
 = 
  
 "ID of the audience" 
 ) 
  
 String 
  
 audienceId 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--csvFile" 
 , 
  
 required 
  
 = 
  
 true 
 , 
  
 description 
  
 = 
  
 "Comma-separated file containing user data to ingest" 
 ) 
  
 String 
  
 csvFile 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--keyUri" 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 description 
  
 = 
  
 "URI of the Google Cloud KMS key for encrypting data. If this parameter is set, you" 
  
 + 
  
 " must also set the --wipProvider parameter." 
 ) 
  
 String 
  
 keyUri 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--wipProvider" 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 description 
  
 = 
  
 "Workload Identity Pool provider name for encrypting data. If this parameter is set," 
  
 + 
  
 " you must also set the --keyUri parameter. The argument for this parameter must" 
  
 + 
  
 " follow the pattern:" 
  
 + 
  
 " projects/PROJECT_ID/locations/global/workloadIdentityPools/WIP_ID/providers/PROVIDER_ID" 
 ) 
  
 String 
  
 wipProvider 
 ; 
  
 @Parameter 
 ( 
  
 names 
  
 = 
  
 "--validateOnly" 
 , 
  
 required 
  
 = 
  
 false 
 , 
  
 arity 
  
 = 
  
 1 
 , 
  
 description 
  
 = 
  
 "Whether to enable validateOnly on the request" 
 ) 
  
 boolean 
  
 validateOnly 
  
 = 
  
 true 
 ; 
  
 } 
  
 public 
  
 static 
  
 void 
  
 main 
 ( 
 String 
 [] 
  
 args 
 ) 
  
 throws 
  
 IOException 
 , 
  
 GeneralSecurityException 
  
 { 
  
 ParamsConfig 
  
 paramsConfig 
  
 = 
  
 new 
  
 ParamsConfig 
 (). 
 parseOrExit 
 ( 
 args 
 ); 
  
 if 
  
 (( 
 paramsConfig 
 . 
 loginAccountId 
  
 == 
  
 null 
 ) 
  
 != 
  
 ( 
 paramsConfig 
 . 
 loginAccountType 
  
 == 
  
 null 
 )) 
  
 { 
  
 throw 
  
 new 
  
 IllegalArgumentException 
 ( 
  
 "Must specify either both or neither of login account ID and login account type" 
 ); 
  
 } 
  
 if 
  
 (( 
 paramsConfig 
 . 
 linkedAccountId 
  
 == 
  
 null 
 ) 
  
 != 
  
 ( 
 paramsConfig 
 . 
 linkedAccountType 
  
 == 
  
 null 
 )) 
  
 { 
  
 throw 
  
 new 
  
 IllegalArgumentException 
 ( 
  
 "Must specify either both or neither of linked account ID and linked account type" 
 ); 
  
 } 
  
 if 
  
 (( 
 paramsConfig 
 . 
 keyUri 
  
 == 
  
 null 
 ) 
  
 != 
  
 ( 
 paramsConfig 
 . 
 wipProvider 
  
 == 
  
 null 
 )) 
  
 { 
  
 throw 
  
 new 
  
 IllegalArgumentException 
 ( 
  
 "Must specify either both or neither of key URI and WIP provider" 
 ); 
  
 } 
  
 new 
  
 IngestAudienceMembers 
 (). 
 runExample 
 ( 
 paramsConfig 
 ); 
  
 } 
  
 /** 
 * Runs the example. 
 * 
 * @param params the parameters for the example 
 */ 
  
 private 
  
 void 
  
 runExample 
 ( 
 ParamsConfig 
  
 params 
 ) 
  
 throws 
  
 IOException 
 , 
  
 GeneralSecurityException 
  
 { 
  
 // Reads member data from the data file. 
  
 List<Member> 
  
 memberList 
  
 = 
  
 readMemberDataFile 
 ( 
 params 
 . 
 csvFile 
 ); 
  
 // Gets an instance of the UserDataFormatter for normalizing and formatting the data. 
  
 UserDataFormatter 
  
 userDataFormatter 
  
 = 
  
 UserDataFormatter 
 . 
 create 
 (); 
  
 // Determines if encryption parameters are set. 
  
 boolean 
  
 useEncryption 
  
 = 
  
 ( 
 params 
 . 
 keyUri 
  
 != 
  
 null 
 && 
 params 
 . 
 wipProvider 
  
 != 
  
 null 
 ); 
  
 Encrypter 
  
 encrypter 
  
 = 
  
 null 
 ; 
  
 if 
  
 ( 
 useEncryption 
 ) 
  
 { 
  
 // Gets an instance of the encryption utility. 
  
 encrypter 
  
 = 
  
 Encrypter 
 . 
 createForGcpKms 
 ( 
 params 
 . 
 keyUri 
 , 
  
 null 
 ); 
  
 } 
  
 // Builds the audience_members collection for the request. 
  
 List<AudienceMember> 
  
 audienceMembers 
  
 = 
  
 new 
  
 ArrayList 
<> (); 
  
 for 
  
 ( 
 Member 
  
 member 
  
 : 
  
 memberList 
 ) 
  
 { 
  
 UserData 
 . 
 Builder 
  
 userDataBuilder 
  
 = 
  
 UserData 
 . 
 newBuilder 
 (); 
  
 // Adds a UserIdentifier for each valid email address for the member. 
  
 for 
  
 ( 
 String 
  
 email 
  
 : 
  
 member 
 . 
 emailAddresses 
 ) 
  
 { 
  
 String 
  
 processedEmail 
 ; 
  
 try 
  
 { 
  
 processedEmail 
  
 = 
  
 useEncryption 
  
 ? 
  
 userDataFormatter 
 . 
 processEmailAddress 
 ( 
 email 
 , 
  
 Encoding 
 . 
 HEX 
 , 
  
 encrypter 
 ) 
  
 : 
  
 userDataFormatter 
 . 
 processEmailAddress 
 ( 
 email 
 , 
  
 Encoding 
 . 
 HEX 
 ); 
  
 } 
  
 catch 
  
 ( 
 IllegalArgumentException 
  
 iae 
 ) 
  
 { 
  
 // Skips invalid input. 
  
 continue 
 ; 
  
 } 
  
 // Sets the email address identifier to the encoded and possibly encrypted email hash. 
  
 userDataBuilder 
 . 
 addUserIdentifiers 
 ( 
  
 UserIdentifier 
 . 
 newBuilder 
 (). 
 setEmailAddress 
 ( 
 processedEmail 
 )); 
  
 } 
  
 // Adds a UserIdentifier for each valid phone number for the member. 
  
 for 
  
 ( 
 String 
  
 phoneNumber 
  
 : 
  
 member 
 . 
 phoneNumbers 
 ) 
  
 { 
  
 String 
  
 processedPhoneNumber 
 ; 
  
 try 
  
 { 
  
 processedPhoneNumber 
  
 = 
  
 useEncryption 
  
 ? 
  
 userDataFormatter 
 . 
 processPhoneNumber 
 ( 
 phoneNumber 
 , 
  
 Encoding 
 . 
 HEX 
 , 
  
 encrypter 
 ) 
  
 : 
  
 userDataFormatter 
 . 
 processPhoneNumber 
 ( 
 phoneNumber 
 , 
  
 Encoding 
 . 
 HEX 
 ); 
  
 } 
  
 catch 
  
 ( 
 IllegalArgumentException 
  
 iae 
 ) 
  
 { 
  
 // Skips invalid input. 
  
 continue 
 ; 
  
 } 
  
 // Sets the phone number identifier to the encoded and possibly encrypted phone number hash. 
  
 userDataBuilder 
 . 
 addUserIdentifiers 
 ( 
  
 UserIdentifier 
 . 
 newBuilder 
 (). 
 setPhoneNumber 
 ( 
 processedPhoneNumber 
 )); 
  
 } 
  
 if 
  
 ( 
 userDataBuilder 
 . 
 getUserIdentifiersCount 
 () 
 > 
 0 
 ) 
  
 { 
  
 audienceMembers 
 . 
 add 
 ( 
 AudienceMember 
 . 
 newBuilder 
 (). 
 setUserData 
 ( 
 userDataBuilder 
 ). 
 build 
 ()); 
  
 } 
  
 } 
  
 // Builds the Destination for the request. 
  
 Destination 
 . 
 Builder 
  
 destinationBuilder 
  
 = 
  
 Destination 
 . 
 newBuilder 
 () 
  
 . 
 setOperatingAccount 
 ( 
  
 ProductAccount 
 . 
 newBuilder 
 () 
  
 . 
 setAccountType 
 ( 
 params 
 . 
 operatingAccountType 
 ) 
  
 . 
 setAccountId 
 ( 
 params 
 . 
 operatingAccountId 
 )) 
  
 . 
 setProductDestinationId 
 ( 
 params 
 . 
 audienceId 
 ); 
  
 if 
  
 ( 
 params 
 . 
 loginAccountType 
  
 != 
  
 null 
 && 
 params 
 . 
 loginAccountId 
  
 != 
  
 null 
 ) 
  
 { 
  
 destinationBuilder 
 . 
 setLoginAccount 
 ( 
  
 ProductAccount 
 . 
 newBuilder 
 () 
  
 . 
 setAccountType 
 ( 
 params 
 . 
 loginAccountType 
 ) 
  
 . 
 setAccountId 
 ( 
 params 
 . 
 loginAccountId 
 )); 
  
 } 
  
 if 
  
 ( 
 params 
 . 
 linkedAccountType 
  
 != 
  
 null 
 && 
 params 
 . 
 linkedAccountId 
  
 != 
  
 null 
 ) 
  
 { 
  
 destinationBuilder 
 . 
 setLinkedAccount 
 ( 
  
 ProductAccount 
 . 
 newBuilder 
 () 
  
 . 
 setAccountType 
 ( 
 params 
 . 
 linkedAccountType 
 ) 
  
 . 
 setAccountId 
 ( 
 params 
 . 
 linkedAccountId 
 )); 
  
 } 
  
 // Configures the EncryptionInfo for the request if encryption parameters provided. 
  
 EncryptionInfo 
  
 encryptionInfo 
  
 = 
  
 null 
 ; 
  
 if 
  
 ( 
 useEncryption 
 ) 
  
 { 
  
 encryptionInfo 
  
 = 
  
 EncryptionInfo 
 . 
 newBuilder 
 () 
  
 . 
 setGcpWrappedKeyInfo 
 ( 
  
 GcpWrappedKeyInfo 
 . 
 newBuilder 
 () 
  
 . 
 setKekUri 
 ( 
 params 
 . 
 keyUri 
 ) 
  
 . 
 setWipProvider 
 ( 
 params 
 . 
 wipProvider 
 ) 
  
 . 
 setKeyType 
 ( 
 KeyType 
 . 
 XCHACHA20_POLY1305 
 ) 
  
 // Sets the encrypted_dek field to the Base64-encoded encrypted DEK. 
  
 . 
 setEncryptedDek 
 ( 
  
 userDataFormatter 
 . 
 base64Encode 
 ( 
  
 encrypter 
 . 
 getEncryptedDek 
 (). 
 toByteArray 
 ()))) 
  
 . 
 build 
 (); 
  
 } 
  
 try 
  
 ( 
 IngestionServiceClient 
  
 ingestionServiceClient 
  
 = 
  
 IngestionServiceClient 
 . 
 create 
 ()) 
  
 { 
  
 int 
  
 requestCount 
  
 = 
  
 0 
 ; 
  
 // Batches requests to send up to the maximum number of audience members per request. 
  
 for 
  
 ( 
 List<AudienceMember> 
  
 audienceMembersBatch 
  
 : 
  
 Lists 
 . 
 partition 
 ( 
 audienceMembers 
 , 
  
 MAX_MEMBERS_PER_REQUEST 
 )) 
  
 { 
  
 requestCount 
 ++ 
 ; 
  
 // Builds the request. 
  
 IngestAudienceMembersRequest 
 . 
 Builder 
  
 requestBuilder 
  
 = 
  
 IngestAudienceMembersRequest 
 . 
 newBuilder 
 () 
  
 . 
 addDestinations 
 ( 
 destinationBuilder 
 ) 
  
 // Adds members from the current batch. 
  
 . 
 addAllAudienceMembers 
 ( 
 audienceMembersBatch 
 ) 
  
 . 
 setConsent 
 ( 
  
 Consent 
 . 
 newBuilder 
 () 
  
 . 
 setAdPersonalization 
 ( 
 ConsentStatus 
 . 
 CONSENT_GRANTED 
 ) 
  
 . 
 setAdUserData 
 ( 
 ConsentStatus 
 . 
 CONSENT_GRANTED 
 )) 
  
 // Sets validate_only. If true, then the Data Manager API only validates the request 
  
 // but doesn't apply changes. 
  
 . 
 setValidateOnly 
 ( 
 params 
 . 
 validateOnly 
 ) 
  
 // Sets encoding to match the encoding used. 
  
 . 
 setEncoding 
 ( 
 com 
 . 
 google 
 . 
 ads 
 . 
 datamanager 
 . 
 v1 
 . 
 Encoding 
 . 
 HEX 
 ) 
  
 . 
 setTermsOfService 
 ( 
  
 TermsOfService 
 . 
 newBuilder 
 () 
  
 . 
 setCustomerMatchTermsOfServiceStatus 
 ( 
 TermsOfServiceStatus 
 . 
 ACCEPTED 
 )); 
  
 if 
  
 ( 
 useEncryption 
 ) 
  
 { 
  
 // Sets encryption info on the request. 
  
 requestBuilder 
 . 
 setEncryptionInfo 
 ( 
 encryptionInfo 
 ); 
  
 } 
  
 IngestAudienceMembersRequest 
  
 request 
  
 = 
  
 requestBuilder 
 . 
 build 
 (); 
  
 IngestAudienceMembersResponse 
  
 response 
  
 = 
  
 ingestionServiceClient 
 . 
 ingestAudienceMembers 
 ( 
 request 
 ); 
  
 if 
  
 ( 
 LOGGER 
 . 
 isLoggable 
 ( 
 Level 
 . 
 INFO 
 )) 
  
 { 
  
 LOGGER 
 . 
 info 
 ( 
 String 
 . 
 format 
 ( 
 "Response for request #%d:%n%s" 
 , 
  
 requestCount 
 , 
  
 response 
 )); 
  
 } 
  
 } 
  
 LOGGER 
 . 
 info 
 ( 
 "# of requests sent: " 
  
 + 
  
 requestCount 
 ); 
  
 } 
  
 } 
  
 /** Data object for a single row of input data. */ 
  
 private 
  
 static 
  
 class 
 Member 
  
 { 
  
 private 
  
 final 
  
 List<String> 
  
 emailAddresses 
  
 = 
  
 new 
  
 ArrayList 
<> (); 
  
 private 
  
 final 
  
 List<String> 
  
 phoneNumbers 
  
 = 
  
 new 
  
 ArrayList 
<> (); 
  
 } 
  
 /** 
 * Reads the data file and parses each line into a {@link IngestAudienceMembers.Member} object. 
 * 
 * @param dataFile the CSV data file 
 * @return a list of Member objects 
 */ 
  
 private 
  
 List<Member> 
  
 readMemberDataFile 
 ( 
 String 
  
 dataFile 
 ) 
  
 throws 
  
 IOException 
  
 { 
  
 List<Member> 
  
 members 
  
 = 
  
 new 
  
 ArrayList 
<> (); 
  
 try 
  
 ( 
 BufferedReader 
  
 reader 
  
 = 
  
 new 
  
 BufferedReader 
 ( 
 new 
  
 FileReader 
 ( 
 dataFile 
 ))) 
  
 { 
  
 String 
  
 line 
 ; 
  
 int 
  
 lineNumber 
  
 = 
  
 0 
 ; 
  
 while 
  
 (( 
 line 
  
 = 
  
 reader 
 . 
 readLine 
 ()) 
  
 != 
  
 null 
 ) 
  
 { 
  
 lineNumber 
 ++ 
 ; 
  
 if 
  
 ( 
 line 
 . 
 startsWith 
 ( 
 "#" 
 )) 
  
 { 
  
 // Skips comment lines. 
  
 continue 
 ; 
  
 } 
  
 // Expected format: 
  
 // email_1,email_2,email_3,phone_1,phone_2,phone_3 
  
 String 
 [] 
  
 columns 
  
 = 
  
 line 
 . 
 split 
 ( 
 "," 
 ); 
  
 if 
  
 ( 
 columns 
 [ 
 0 
 ] 
 . 
 equals 
 ( 
 "email_1" 
 )) 
  
 { 
  
 // Skips header row. 
  
 continue 
 ; 
  
 } 
  
 Member 
  
 member 
  
 = 
  
 new 
  
 Member 
 (); 
  
 for 
  
 ( 
 int 
  
 col 
  
 = 
  
 0 
 ; 
  
 col 
 < 
 columns 
 . 
 length 
 ; 
  
 col 
 ++ 
 ) 
  
 { 
  
 if 
  
 ( 
 columns 
 [ 
 col 
 ] 
  
 == 
  
 null 
  
 || 
  
 columns 
 [ 
 col 
 ] 
 . 
 trim 
 (). 
 isEmpty 
 ()) 
  
 { 
  
 // Skips blank value for the row and column. 
  
 continue 
 ; 
  
 } 
  
 // Parses the row, ignoring anything beyond column index 5. 
  
 if 
  
 ( 
 col 
 < 
 3 
 ) 
  
 { 
  
 member 
 . 
 emailAddresses 
 . 
 add 
 ( 
 columns 
 [ 
 col 
 ] 
 ); 
  
 } 
  
 else 
  
 if 
  
 ( 
 col 
 < 
 6 
 ) 
  
 { 
  
 member 
 . 
 phoneNumbers 
 . 
 add 
 ( 
 columns 
 [ 
 col 
 ] 
 ); 
  
 } 
  
 else 
  
 { 
  
 LOGGER 
 . 
 warning 
 ( 
 "Ignoring column index " 
  
 + 
  
 col 
  
 + 
  
 " in line #" 
  
 + 
  
 lineNumber 
 ); 
  
 } 
  
 } 
  
 if 
  
 ( 
 member 
 . 
 emailAddresses 
 . 
 isEmpty 
 () 
 && 
 member 
 . 
 phoneNumbers 
 . 
 isEmpty 
 ()) 
  
 { 
  
 // Skips the row since it contains no user data. 
  
 LOGGER 
 . 
 warning 
 ( 
 String 
 . 
 format 
 ( 
 "Ignoring line %d. No data." 
 , 
  
 lineNumber 
 )); 
  
 } 
  
 else 
  
 { 
  
 // Adds the parsed user data to the list. 
  
 members 
 . 
 add 
 ( 
 member 
 ); 
  
 } 
  
 } 
  
 } 
  
 return 
  
 members 
 ; 
  
 } 
 } 
  

Node

 #!/usr/bin/env node 
 // Copyright 2025 Google LLC 
 // 
 // Licensed under the Apache License, Version 2.0 (the "License"); 
 // you may not use this file except in compliance with the License. 
 // You may obtain a copy of the License at 
 // 
 //     https://www.apache.org/licenses/LICENSE-2.0 
 // 
 // Unless required by applicable law or agreed to in writing, software 
 // distributed under the License is distributed on an "AS IS" BASIS, 
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 // See the License for the specific language governing permissions and 
 // limitations under the License. 
 'use strict' 
 ; 
 import 
  
 { 
 IngestionServiceClient 
 } 
  
 from 
  
 '@google-ads/datamanager' 
 ; 
 import 
  
 { 
 protos 
 } 
  
 from 
  
 '@google-ads/datamanager' 
 ; 
 const 
  
 { 
  
 AudienceMember 
 , 
  
 Destination 
 , 
  
 Encoding 
 : 
  
 DataManagerEncoding 
 , 
  
 Consent 
 , 
  
 ConsentStatus 
 , 
  
 IngestAudienceMembersRequest 
 , 
  
 ProductAccount 
 , 
  
 TermsOfService 
 , 
  
 TermsOfServiceStatus 
 , 
  
 UserData 
 , 
  
 UserIdentifier 
 , 
 } 
  
 = 
  
 protos 
 . 
 google 
 . 
 ads 
 . 
 datamanager 
 . 
 v1 
 ; 
 import 
  
 { 
 UserDataFormatter 
 , 
  
 Encoding 
 } 
  
 from 
  
 '@google-ads/data-manager-util' 
 ; 
 import 
  
 * 
  
 as 
  
 csv 
  
 from 
  
 'csv-parser' 
 ; 
 import 
  
 * 
  
 as 
  
 fs 
  
 from 
  
 'fs' 
 ; 
 import 
  
 * 
  
 as 
  
 yargs 
  
 from 
  
 'yargs' 
 ; 
 const 
  
 MAX_MEMBERS_PER_REQUEST 
  
 = 
  
 10000 
 ; 
 interface 
  
 Arguments 
  
 { 
  
 operating_account_type 
 : 
  
 string 
 ; 
  
 operating_account_id 
 : 
  
 string 
 ; 
  
 audience_id 
 : 
  
 string 
 ; 
  
 csv_file 
 : 
  
 string 
 ; 
  
 validate_only 
 : 
  
 boolean 
 ; 
  
 login_account_type 
 ?: 
  
 string 
 ; 
  
 login_account_id 
 ?: 
  
 string 
 ; 
  
 linked_account_type 
 ?: 
  
 string 
 ; 
  
 linked_account_id 
 ?: 
  
 string 
 ; 
  
 [ 
 x 
 : 
  
 string 
 ] 
 : 
  
 unknown 
 ; 
 } 
 interface 
  
 MemberRow 
  
 { 
  
 emails 
 : 
  
 string 
 []; 
  
 phoneNumbers 
 : 
  
 string 
 []; 
 } 
 /** 
 * The main function for the IngestAudienceMembers sample. 
 */ 
 async 
  
 function 
  
 main 
 () 
  
 { 
  
 const 
  
 argv 
 : 
  
 Arguments 
  
 = 
  
 yargs 
  
 . 
 option 
 ( 
 'operating_account_type' 
 , 
  
 { 
  
 describe 
 : 
  
 'The account type of the operating account.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 required 
 : 
  
 true 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'operating_account_id' 
 , 
  
 { 
  
 describe 
 : 
  
 'The ID of the operating account.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 required 
 : 
  
 true 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'audience_id' 
 , 
  
 { 
  
 describe 
 : 
  
 'The ID of the destination audience.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 required 
 : 
  
 true 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'csv_file' 
 , 
  
 { 
  
 describe 
 : 
  
 'Comma-separated file containing user data to ingest.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 required 
 : 
  
 true 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'validate_only' 
 , 
  
 { 
  
 describe 
 : 
  
 'Whether to enable validate_only on the request.' 
 , 
  
 type 
 : 
  
 'boolean' 
 , 
  
 default 
 : 
  
 true 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'login_account_type' 
 , 
  
 { 
  
 describe 
 : 
  
 'The account type of the login account.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'login_account_id' 
 , 
  
 { 
  
 describe 
 : 
  
 'The ID of the login account.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'linked_account_type' 
 , 
  
 { 
  
 describe 
 : 
  
 'The account type of the linked account.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'linked_account_id' 
 , 
  
 { 
  
 describe 
 : 
  
 'The ID of the linked account.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 }) 
  
 . 
 option 
 ( 
 'config' 
 , 
  
 { 
  
 describe 
 : 
  
 'Path to a JSON file with arguments.' 
 , 
  
 type 
 : 
  
 'string' 
 , 
  
 }) 
  
 . 
 config 
 ( 
 'config' 
 ) 
  
 . 
 check 
 (( 
 args 
 : 
  
 Arguments 
 ) 
  
 = 
>  
 { 
  
 if 
  
 ( 
  
 ( 
 args 
 . 
 login_account_type 
 && 
 ! 
 args 
 . 
 login_account_id 
 ) 
  
 || 
  
 ( 
 ! 
 args 
 . 
 login_account_type 
 && 
 args 
 . 
 login_account_id 
 ) 
  
 ) 
  
 { 
  
 throw 
  
 new 
  
 Error 
 ( 
  
 'Must specify either both or neither of login account type and ' 
  
 + 
  
 'login account ID' 
 , 
  
 ); 
  
 } 
  
 if 
  
 ( 
  
 ( 
 args 
 . 
 linked_account_type 
 && 
 ! 
 args 
 . 
 linked_account_id 
 ) 
  
 || 
  
 ( 
 ! 
 args 
 . 
 linked_account_type 
 && 
 args 
 . 
 linked_account_id 
 ) 
  
 ) 
  
 { 
  
 throw 
  
 new 
  
 Error 
 ( 
  
 'Must specify either both or neither of linked account ' 
  
 + 
  
 'type and linked account ID' 
 , 
  
 ); 
  
 } 
  
 return 
  
 true 
 ; 
  
 }) 
  
 . 
 parseSync 
 (); 
  
 const 
  
 formatter 
  
 = 
  
 new 
  
 UserDataFormatter 
 (); 
  
 const 
  
 memberRows 
 : 
  
 MemberRow 
 [] 
  
 = 
  
 await 
  
 readMemberDataFile 
 ( 
 argv 
 . 
 csv_file 
 ); 
  
 // Builds the audience_members collection for the request. 
  
 const 
  
 audienceMembers 
  
 = 
  
 []; 
  
 for 
  
 ( 
 const 
  
 memberRow 
  
 of 
  
 memberRows 
 ) 
  
 { 
  
 const 
  
 userData 
  
 = 
  
 UserData 
 . 
 create 
 (); 
  
 // Adds a UserIdentifier for each valid email address for the member. 
  
 for 
  
 ( 
 const 
  
 email 
  
 of 
  
 memberRow 
 . 
 emails 
 ) 
  
 { 
  
 try 
  
 { 
  
 const 
  
 processedEmail 
  
 = 
  
 formatter 
 . 
 processEmailAddress 
 ( 
  
 email 
 , 
  
 Encoding 
 . 
 HEX 
 , 
  
 ); 
  
 userData 
 . 
 userIdentifiers 
 . 
 push 
 ( 
  
 UserIdentifier 
 . 
 create 
 ({ 
 emailAddress 
 : 
  
 processedEmail 
 }), 
  
 ); 
  
 } 
  
 catch 
  
 ( 
 e 
 ) 
  
 { 
  
 // Skip invalid input. 
  
 } 
  
 } 
  
 // Adds a UserIdentifier for each valid phone number for the member. 
  
 for 
  
 ( 
 const 
  
 phone 
  
 of 
  
 memberRow 
 . 
 phoneNumbers 
 ) 
  
 { 
  
 try 
  
 { 
  
 const 
  
 processedPhone 
  
 = 
  
 formatter 
 . 
 processPhoneNumber 
 ( 
  
 phone 
 , 
  
 Encoding 
 . 
 HEX 
 , 
  
 ); 
  
 userData 
 . 
 userIdentifiers 
 . 
 push 
 ( 
  
 UserIdentifier 
 . 
 create 
 ({ 
 phoneNumber 
 : 
  
 processedPhone 
 }), 
  
 ); 
  
 } 
  
 catch 
  
 ( 
 e 
 ) 
  
 { 
  
 // Skip invalid input. 
  
 } 
  
 } 
  
 if 
  
 ( 
 userData 
 . 
 userIdentifiers 
 . 
 length 
 > 
 0 
 ) 
  
 { 
  
 audienceMembers 
 . 
 push 
 ( 
 AudienceMember 
 . 
 create 
 ({ 
 userData 
 : 
  
 userData 
 })); 
  
 } 
  
 else 
  
 { 
  
 console 
 . 
 warn 
 ( 
 'Ignoring line. No data.' 
 ); 
  
 } 
  
 } 
  
 // Sets up the Destination. 
  
 const 
  
 operatingAccountType 
  
 = 
  
 convertToAccountType 
 ( 
  
 argv 
 . 
 operating_account_type 
 , 
  
 'operating_account_type' 
 , 
  
 ); 
  
 const 
  
 destination 
  
 = 
  
 Destination 
 . 
 create 
 ({ 
  
 operatingAccount 
 : 
  
 ProductAccount 
 . 
 create 
 ({ 
  
 accountType 
 : 
  
 operatingAccountType 
 , 
  
 accountId 
 : 
  
 argv 
 . 
 operating_account_id 
 , 
  
 }), 
  
 productDestinationId 
 : 
  
 argv 
 . 
 audience_id 
 , 
  
 }); 
  
 // The login account is optional. 
  
 if 
  
 ( 
 argv 
 . 
 login_account_type 
 ) 
  
 { 
  
 const 
  
 loginAccountType 
  
 = 
  
 convertToAccountType 
 ( 
  
 argv 
 . 
 login_account_type 
 , 
  
 'login_account_type' 
 , 
  
 ); 
  
 destination 
 . 
 loginAccount 
  
 = 
  
 ProductAccount 
 . 
 create 
 ({ 
  
 accountType 
 : 
  
 loginAccountType 
 , 
  
 accountId 
 : 
  
 argv 
 . 
 login_account_id 
 , 
  
 }); 
  
 } 
  
 // The linked account is optional. 
  
 if 
  
 ( 
 argv 
 . 
 linked_account_type 
 ) 
  
 { 
  
 const 
  
 linkedAccountType 
  
 = 
  
 convertToAccountType 
 ( 
  
 argv 
 . 
 linked_account_type 
 , 
  
 'linked_account_type' 
 , 
  
 ); 
  
 destination 
 . 
 linkedAccount 
  
 = 
  
 ProductAccount 
 . 
 create 
 ({ 
  
 accountType 
 : 
  
 linkedAccountType 
 , 
  
 accountId 
 : 
  
 argv 
 . 
 linked_account_id 
 , 
  
 }); 
  
 } 
  
 const 
  
 client 
  
 = 
  
 new 
  
 IngestionServiceClient 
 (); 
  
 let 
  
 requestCount 
  
 = 
  
 0 
 ; 
  
 // Batches requests to send up to the maximum number of audience members per request. 
  
 for 
  
 ( 
 let 
  
 i 
  
 = 
  
 0 
 ; 
  
 i 
 < 
 audienceMembers 
 . 
 length 
 ; 
  
 i 
  
 += 
  
 MAX_MEMBERS_PER_REQUEST 
 ) 
  
 { 
  
 requestCount 
 ++ 
 ; 
  
 const 
  
 audienceMembersBatch 
  
 = 
  
 audienceMembers 
 . 
 slice 
 ( 
  
 i 
 , 
  
 i 
  
 + 
  
 MAX_MEMBERS_PER_REQUEST 
 , 
  
 ); 
  
 const 
  
 request 
  
 = 
  
 IngestAudienceMembersRequest 
 . 
 create 
 ({ 
  
 destinations 
 : 
  
 [ 
 destination 
 ], 
  
 // Adds members from the current batch. 
  
 audienceMembers 
 : 
  
 audienceMembersBatch 
 , 
  
 consent 
 : 
  
 Consent 
 . 
 create 
 ({ 
  
 adUserData 
 : 
  
 ConsentStatus 
 . 
 CONSENT_GRANTED 
 , 
  
 adPersonalization 
 : 
  
 ConsentStatus 
 . 
 CONSENT_GRANTED 
 , 
  
 }), 
  
 termsOfService 
 : 
  
 TermsOfService 
 . 
 create 
 ({ 
  
 customerMatchTermsOfServiceStatus 
 : 
  
 TermsOfServiceStatus 
 . 
 ACCEPTED 
 , 
  
 }), 
  
 // Sets encoding to match the encoding used. 
  
 encoding 
 : 
  
 DataManagerEncoding 
 . 
 HEX 
 , 
  
 // Sets validate_only. If true, then the Data Manager API only validates the request 
  
 // but doesn't apply changes. 
  
 validateOnly 
 : 
  
 argv 
 . 
 validate_only 
 , 
  
 }); 
  
 const 
  
 [ 
 response 
 ] 
  
 = 
  
 await 
  
 client 
 . 
 ingestAudienceMembers 
 ( 
 request 
 ); 
  
 console 
 . 
 log 
 ( 
 `Response for request # 
 ${ 
 requestCount 
 } 
 :\n ` 
 , 
  
 response 
 ); 
  
 } 
  
 console 
 . 
 log 
 ( 
 `# of requests sent: 
 ${ 
 requestCount 
 } 
 ` 
 ); 
 } 
 /** 
 * Reads the user data from the given CSV file. 
 * @param {string} csvFile The path to the CSV file. 
 * @return {Promise<MemberRow[]>} A promise that resolves with an array of user data. 
 */ 
 function 
  
 readMemberDataFile 
 ( 
 csvFile 
 : 
  
 string 
 ) 
 : 
  
 Promise<MemberRow 
 [] 
>  
 { 
  
 return 
  
 new 
  
 Promise 
 (( 
 resolve 
 , 
  
 reject 
 ) 
  
 = 
>  
 { 
  
 const 
  
 members 
 : 
  
 MemberRow 
 [] 
  
 = 
  
 []; 
  
 fs 
 . 
 createReadStream 
 ( 
 csvFile 
 ) 
  
 . 
 pipe 
 ( 
 csv 
 ()) 
  
 . 
 on 
 ( 
 'data' 
 , 
  
 row 
  
 = 
>  
 { 
  
 const 
  
 member 
 : 
  
 MemberRow 
  
 = 
  
 { 
 emails 
 : 
  
 [], 
  
 phoneNumbers 
 : 
  
 []}; 
  
 for 
  
 ( 
 const 
  
 [ 
 fieldName 
 , 
  
 fieldValue 
 ] 
  
 of 
  
 Object 
 . 
 entries 
 ( 
 row 
 )) 
  
 { 
  
 if 
  
 ( 
 ! 
 fieldName 
 ) 
  
 { 
  
 continue 
 ; 
  
 } 
  
 const 
  
 value 
  
 = 
  
 ( 
 fieldValue 
  
 as 
  
 string 
 ). 
 trim 
 (); 
  
 if 
  
 ( 
 value 
  
 === 
  
 '' 
 ) 
  
 { 
  
 continue 
 ; 
  
 } 
  
 if 
  
 ( 
 fieldName 
 . 
 startsWith 
 ( 
 'email_' 
 )) 
  
 { 
  
 member 
 . 
 emails 
 . 
 push 
 ( 
 value 
 ); 
  
 } 
  
 else 
  
 if 
  
 ( 
 fieldName 
 . 
 startsWith 
 ( 
 'phone_' 
 )) 
  
 { 
  
 member 
 . 
 phoneNumbers 
 . 
 push 
 ( 
 value 
 ); 
  
 } 
  
 else 
  
 { 
  
 console 
 . 
 warn 
 ( 
 `Ignoring unrecognized field: 
 ${ 
 fieldName 
 } 
 ` 
 ); 
  
 } 
  
 } 
  
 if 
  
 ( 
 member 
 . 
 emails 
 . 
 length 
 > 
 0 
  
 || 
  
 member 
 . 
 phoneNumbers 
 . 
 length 
 > 
 0 
 ) 
  
 { 
  
 members 
 . 
 push 
 ( 
 member 
 ); 
  
 } 
  
 else 
  
 { 
  
 console 
 . 
 warn 
 ( 
 'Ignoring line. No data.' 
 ); 
  
 } 
  
 }) 
  
 . 
 on 
 ( 
 'end' 
 , 
  
 () 
  
 = 
>  
 { 
  
 resolve 
 ( 
 members 
 ); 
  
 }) 
  
 . 
 on 
 ( 
 'error' 
 , 
  
 error 
  
 = 
>  
 { 
  
 reject 
 ( 
 error 
 ); 
  
 }); 
  
 }); 
 } 
 /** 
 * Validates that a given string is an enum value for the AccountType enum, and 
 * if validation passes, returns the AccountType enum value. 
 * @param proposedValue the name of an AccountType enum value 
 * @param paramName the name of the parameter to use in the error message if validation fails 
 * @returns {protos.google.ads.datamanager.v1.ProductAccount.AccountType} The corresponding enum value. 
 * @throws {Error} If the string is not an AccountType enum value. 
 */ 
 function 
  
 convertToAccountType 
 ( 
  
 proposedValue 
 : 
  
 string 
 , 
  
 paramName 
 : 
  
 string 
 , 
 ) 
 : 
  
 protos 
 . 
 google 
 . 
 ads 
 . 
 datamanager 
 . 
 v1 
 . 
 ProductAccount 
 . 
 AccountType 
  
 { 
  
 const 
  
 AccountType 
  
 = 
  
 ProductAccount 
 . 
 AccountType 
 ; 
  
 const 
  
 accountTypeEnumNames 
  
 = 
  
 Object 
 . 
 keys 
 ( 
 AccountType 
 ). 
 filter 
 ( 
 key 
  
 = 
>  
 isNaN 
 ( 
 Number 
 ( 
 key 
 )), 
  
 ); 
  
 if 
  
 ( 
 ! 
 accountTypeEnumNames 
 . 
 includes 
 ( 
 proposedValue 
 )) 
  
 { 
  
 throw 
  
 new 
  
 Error 
 ( 
 `Invalid 
 ${ 
 paramName 
 } 
 : 
 ${ 
 proposedValue 
 } 
 ` 
 ); 
  
 } 
  
 return 
  
 AccountType 
 [ 
 proposedValue 
  
 as 
  
 keyof 
  
 typeof 
  
 AccountType 
 ]; 
 } 
 if 
  
 ( 
 require 
 . 
 main 
  
 === 
  
 module 
 ) 
  
 { 
  
 main 
 (). 
 catch 
 ( 
 console 
 . 
 error 
 ); 
 } 
  

PHP

< ?php 
 // Copyright 2025 Google LLC 
 // 
 // Licensed under the Apache License, Version 2.0 (the "License"); 
 // you may not use this file except in compliance with the License. 
 // You may obtain a copy of the License at 
 // 
 //     https://www.apache.org/licenses/LICENSE-2.0 
 // 
 // Unless required by applicable law or agreed to in writing, software 
 // distributed under the License is distributed on an "AS IS" BASIS, 
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 // See the License for the specific language governing permissions and 
 // limitations under the License. 
 /** 
 * Sample of sending an IngestAudienceMembersRequest without encryption. 
 */ 
 require_once dirname(__DIR__, 1) . '/vendor/autoload.php'; 
 use Google\Ads\DataManager\V1\AudienceMember; 
 use Google\Ads\DataManager\V1\Client\IngestionServiceClient; 
 use Google\Ads\DataManager\V1\Consent; 
 use Google\Ads\DataManager\V1\ConsentStatus; 
 use Google\Ads\DataManager\V1\Destination; 
 use Google\Ads\DataManager\V1\Encoding as DataManagerEncoding; 
 use Google\Ads\DataManager\V1\IngestAudienceMembersRequest; 
 use Google\Ads\DataManager\V1\ProductAccount; 
 use Google\Ads\DataManager\V1\ProductAccount\AccountType; 
 use Google\Ads\DataManager\V1\TermsOfService; 
 use Google\Ads\DataManager\V1\TermsOfServiceStatus; 
 use Google\Ads\DataManager\V1\UserData; 
 use Google\Ads\DataManager\V1\UserIdentifier; 
 use Google\Ads\DataManagerUtil\Encoding; 
 use Google\Ads\DataManagerUtil\Formatter; 
 use Google\ApiCore\ApiException; 
 /** 
 * Reads the comma-separated member data file. 
 * 
 * @param string $csvFile The member data file. Expected format is one comma-separated row 
 * per audience member, with a header row containing headers of the form 
 * "email_..." or "phone_...". 
 * @return array A list of associative arrays, each representing a member. 
 */ 
 function readMemberDataFile(string $csvFile): array 
 { 
 $members = []; 
 if (($handle = fopen($csvFile, 'r')) !== false) { 
 $header = fgetcsv($handle); 
 $lineNum = 0; 
 while (($row = fgetcsv($handle)) !== false) { 
 $lineNum++; 
 $member = [ 
 'emails' => [], 
 'phone_numbers' => [], 
 ]; 
 $rowData = array_combine($header, $row); // Combine header with row data 
 foreach ($rowData as $fieldName => $fieldValue) { 
 if ($fieldName === null || $fieldName === '') { 
 // Ignores trailing field without a corresponding header. 
 continue; 
 } 
 $fieldValue = trim($fieldValue); 
 if (strlen($fieldValue) === 0) { 
 // Ignores blank/empty value. 
 continue; 
 } 
 if (str_starts_with($fieldName, 'email_')) { 
 $member['emails'][] = $fieldValue; 
 } elseif (str_starts_with($fieldName, 'phone_')) { 
 $member['phone_numbers'][] = $fieldValue; 
 } else { 
 error_log(sprintf('Ignoring unrecognized field: %s', $fieldName)); 
 } 
 } 
 if (!empty($member['emails']) || !empty($member['phone_numbers'])) { 
 $members[] = $member; 
 } else { 
 error_log(sprintf('Ignoring line #%d. No data.', $lineNum)); 
 } 
 } 
 fclose($handle); 
 } else { 
 throw new \RuntimeException(sprintf('Could not open CSV file: %s', $csvFile)); 
 } 
 return $members; 
 } 
 /** 
 * Runs the sample. 
 * 
 * @param int $operatingAccountType The account type of the operating account. 
 * @param string $operatingAccountId The ID of the operating account. 
 * @param string $audienceId The ID of the destination audience. 
 * @param string $csvFile The CSV file containing member data. 
 * @param bool $validateOnly Whether to enable validateOnly on the request. 
 * @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. 
 */ 
 function main( 
 int $operatingAccountType, 
 string $operatingAccountId, 
 string $audienceId, 
 string $csvFile, 
 bool $validateOnly, 
 ?int $loginAccountType = null, 
 ?string $loginAccountId = null, 
 ?int $linkedAccountType = null, 
 ?string $linkedAccountId = null 
 ): void { 
 // Reads member data from the data file. 
 $memberRows = readMemberDataFile($csvFile); 
 // Gets an instance of the UserDataFormatter for normalizing and formatting the data. 
 $formatter = new Formatter(); 
 // Builds the audience_members collection for the request. 
 $audienceMembers = []; 
 foreach ($memberRows as $memberRow) { 
 $identifiers = []; 
 // Adds a UserIdentifier for each valid email address for the member. 
 foreach ($memberRow['emails'] as $email) { 
 try { 
 // Formats, hashes, and encodes the email address. 
 $processedEmail = $formatter->processEmailAddress($email, Encoding::Hex); 
 // Sets the email address identifier to the encoded email hash. 
 $identifiers[] = (new UserIdentifier())->setEmailAddress($processedEmail); 
 } catch (\InvalidArgumentException $e) { 
 // Skips invalid input. 
 error_log(sprintf('Skipping invalid email: %s', $e->getMessage())); 
 continue; 
 } 
 } 
 // Adds a UserIdentifier for each valid phone number for the member. 
 foreach ($memberRow['phone_numbers'] as $phone) { 
 try { 
 // Formats, hashes, and encodes the phone number. 
 $processedPhone = $formatter->processPhoneNumber($phone, Encoding::Hex); 
 // Sets the phone number identifier to the encoded phone number hash. 
 $identifiers[] = (new UserIdentifier())->setPhoneNumber($processedPhone); 
 } catch (\InvalidArgumentException $e) { 
 // Skips invalid input. 
 error_log(sprintf('Skipping invalid phone: %s', $e->getMessage())); 
 continue; 
 } 
 } 
 if (!empty($identifiers)) { 
 $userData = new UserData()->setUserIdentifiers($identifiers); 
 // Adds an AudienceMember with the formatted and hashed identifiers. 
 $audienceMember = (new AudienceMember())->setUserData($userData); 
 $audienceMembers[] = $audienceMember; 
 } 
 } 
 // Builds the destination for the request. 
 $destination = new Destination(); 
 $destination->setOperatingAccount(new ProductAccount() 
 ->setAccountType($operatingAccountType) 
 ->setAccountId($operatingAccountId)); 
 if ($loginAccountType !== null && $loginAccountId !== null) { 
 $destination->setLoginAccount(new ProductAccount() 
 ->setAccountType($loginAccountType) 
 ->setAccountId($loginAccountId)); 
 } 
 if ($linkedAccountType !== null && $linkedAccountId !== null) { 
 $destination->setLinkedAccount(new ProductAccount() 
 ->setAccountType($linkedAccountType) 
 ->setAccountId($linkedAccountId)); 
 } 
 $destination->setProductDestinationId($audienceId); 
 // Builds the request. 
 $request = (new IngestAudienceMembersRequest()) 
 ->setDestinations([$destination]) 
 ->setAudienceMembers($audienceMembers) 
 ->setConsent((new Consent()) 
 ->setAdUserData(ConsentStatus::CONSENT_GRANTED) 
 ->setAdPersonalization(ConsentStatus::CONSENT_GRANTED) 
 ) 
 ->setTermsOfService((new TermsOfService()) 
 ->setCustomerMatchTermsOfServiceStatus(TermsOfServiceStatus::ACCEPTED) 
 ) 
 // Sets encoding to match the encoding used. 
 ->setEncoding(DataManagerEncoding::HEX) 
 // Sets validate_only to true to validate but not apply the changes in the request. 
 ->setValidateOnly(true); 
 // Creates a client for the ingestion service. 
 $client = new IngestionServiceClient(); 
 try { 
 // Sends the request. 
 $response = $client->ingestAudienceMembers($request); 
 echo "Response:\n" . json_encode(json_decode($response->serializeToJsonString()), JSON_PRETTY_PRINT) . "\n"; 
 } catch (ApiException $e) { 
 echo 'Error sending request: ' . $e->getMessage() . "\n"; 
 } finally { 
 $client->close(); 
 } 
 } 
 // Command-line argument parsing 
 $options = getopt( 
 '', 
 [ 
 'operating_account_type:', 
 'operating_account_id:', 
 'login_account_type::', 
 'login_account_id::', 
 'linked_account_type::', 
 'linked_account_id::', 
 'audience_id:', 
 'csv_file:', 
 'validate_only::' 
 ] 
 ); 
 $operatingAccountType = $options['operating_account_type'] ?? null; 
 $operatingAccountId = $options['operating_account_id'] ?? null; 
 $audienceId = $options['audience_id'] ?? null; 
 $csvFile = $options['csv_file'] ?? null; 
 // Only validates requests by default. 
 $validateOnly = true; 
 if (array_key_exists('validate_only', $options)) { 
 $value = $options['validate_only']; 
 // `getopt` with `::` returns boolean `false` if the option is passed without a value. 
 if ($value === false || !in_array($value, ['true', 'false'], true)) { 
 echo "Error: --validate_only requires a value of 'true' or 'false'.\n"; 
 exit(1); 
 } 
 $validateOnly = ($value === 'true'); 
 } 
 if (empty($operatingAccountType) || empty($operatingAccountId) || empty($audienceId) || empty($csvFile)) { 
 echo 'Usage: php ingest_audience_members.php ' . 
 '--operating_account_type=<account_type> ' . 
 '--operating_account_id=<account_id> ' . 
 '--audience_id=<audience_id> ' . 
 "--csv_file=<path_to_csv>\n" . 
 'Optional: --login_account_type=<account_type> --login_account_id=<account_id> ' . 
 '--linked_account_type=<account_type> --linked_account_id=<account_id> ' . 
 "--validate_only=<true|false>\n"; 
 exit(1); 
 } 
 // Converts the operating account type string to an AccountType enum. 
 $parsedOperatingAccountType = AccountType::value($operatingAccountType); 
 if (isset($options['login_account_type']) != isset($options['login_account_id'])) { 
 throw new \InvalidArgumentException( 
 'Must specify either both or neither of login account type and login account ID' 
 ); 
 } 
 $parsedLoginAccountType = null; 
 if (isset($options['login_account_type'])) { 
 // Converts the login account type string to an AccountType enum. 
 $parsedLoginAccountType = AccountType::value($options['login_account_type']); 
 } 
 if (isset($options['linked_account_type']) != isset($options['linked_account_id'])) { 
 throw new \InvalidArgumentException( 
 'Must specify either both or neither of linked account type and linked account ID' 
 ); 
 } 
 $parsedLinkedAccountType = null; 
 if (isset($options['linked_account_type'])) { 
 // Converts the linked account type string to an AccountType enum. 
 $parsedLinkedAccountType = AccountType::value($options['linked_account_type']); 
 } 
 main( 
 $parsedOperatingAccountType, 
 $operatingAccountId, 
 $audienceId, 
 $csvFile, 
 $validateOnly, 
 $parsedLoginAccountType, 
 $options['login_account_id'] ?? null, 
 $parsedLinkedAccountType, 
 $options['linked_account_id'] ?? null 
 ); 
  

Python

 #!/usr/bin/env python 
 # Copyright 2025 Google LLC 
 # 
 # Licensed under the Apache License, Version 2.0 (the "License"); 
 # you may not use this file except in compliance with the License. 
 # You may obtain a copy of the License at 
 # 
 #     https://www.apache.org/licenses/LICENSE-2.0 
 # 
 # Unless required by applicable law or agreed to in writing, software 
 # distributed under the License is distributed on an "AS IS" BASIS, 
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 # See the License for the specific language governing permissions and 
 # limitations under the License. 
 """Sample of sending an IngestAudienceMembersRequest with the option to use encryption.""" 
 import 
  
 argparse 
 import 
  
 csv 
 import 
  
 logging 
 from 
  
 typing 
  
 import 
 Dict 
 , 
 List 
 , 
 Optional 
 from 
  
 google.ads 
  
 import 
 datamanager_v1 
 from 
  
 google.ads.datamanager_util 
  
 import 
 Encrypter 
 from 
  
 google.ads.datamanager_util 
  
 import 
 Formatter 
 from 
  
 google.ads.datamanager_util.format 
  
 import 
 Encoding 
 _logger 
 = 
 logging 
 . 
 getLogger 
 ( 
 __name__ 
 ) 
 # The maximum number of audience members allowed per request. 
 _MAX_MEMBERS_PER_REQUEST 
 = 
 10_000 
 def 
  
 main 
 ( 
 operating_account_type 
 : 
 datamanager_v1 
 . 
 ProductAccount 
 . 
 AccountType 
 , 
 operating_account_id 
 : 
 str 
 , 
 audience_id 
 : 
 str 
 , 
 csv_file 
 : 
 str 
 , 
 validate_only 
 : 
 bool 
 , 
 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_uri 
 : 
 str 
 = 
 None 
 , 
 wip_provider 
 : 
 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. 
 audience_id: the ID of the destination audience. 
 csv_file: the CSV file containing member data. 
 validate_only: whether to enable validate_only on the request. 
 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_uri: the URI of the Google Cloud KMS key. 
 wip_provider: the Workload Identity Pool provider name. Must follow the pattern: 
 projects/PROJECT_ID/locations/global/workloadIdentityPools/WIP_ID/providers/PROVIDER_ID 
 """ 
 # Validates parameter pairs. 
 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 
 bool 
 ( 
 key_uri 
 ) 
 != 
 bool 
 ( 
 wip_provider 
 ): 
 raise 
 ValueError 
 ( 
 "Must specify either both or neither of key URI and WIP provider" 
 ) 
 # Gets an instance of the formatter. 
 formatter 
 : 
 Formatter 
 = 
 Formatter 
 () 
 # Determines if encryption parameters are set. 
 use_encryption 
 : 
 bool 
 = 
 key_uri 
 and 
 wip_provider 
 encrypter 
 : 
 Encrypter 
 = 
 None 
 if 
 use_encryption 
 : 
 # Creates an instance of the encryption utility. 
 encrypter 
 = 
 Encrypter 
 . 
 create_for_gcp_kms 
 ( 
 key_uri 
 ) 
 # Reads the input file. 
 member_rows 
 : 
 List 
 [ 
 Dict 
 [ 
 str 
 , 
 str 
 ]] 
 = 
 read_member_data_file 
 ( 
 csv_file 
 ) 
 audience_members 
 : 
 List 
 [ 
 datamanager_v1 
 . 
 AudienceMember 
 ] 
 = 
 [] 
 member_row 
 : 
 Dict 
 [ 
 str 
 , 
 str 
 ] 
 for 
 member_row 
 in 
 member_rows 
 : 
 user_data 
 = 
 datamanager_v1 
 . 
 UserData 
 () 
 email 
 : 
 str 
 for 
 email 
 in 
 member_row 
 [ 
 "emails" 
 ]: 
 try 
 : 
 processed_email 
 : 
 str 
 = 
 formatter 
 . 
 process_email_address 
 ( 
 email 
 , 
 Encoding 
 . 
 HEX 
 , 
 encrypter 
 ) 
 user_data 
 . 
 user_identifiers 
 . 
 append 
 ( 
 datamanager_v1 
 . 
 UserIdentifier 
 ( 
 email_address 
 = 
 processed_email 
 ) 
 ) 
 except 
 ValueError 
 : 
 # Skips invalid input. 
 continue 
 phone 
 : 
 str 
 for 
 phone 
 in 
 member_row 
 [ 
 "phone_numbers" 
 ]: 
 try 
 : 
 processed_phone 
 : 
 str 
 = 
 formatter 
 . 
 process_phone_number 
 ( 
 phone 
 , 
 Encoding 
 . 
 HEX 
 , 
 encrypter 
 ) 
 user_data 
 . 
 user_identifiers 
 . 
 append 
 ( 
 datamanager_v1 
 . 
 UserIdentifier 
 ( 
 phone_number 
 = 
 processed_phone 
 ) 
 ) 
 except 
 ValueError 
 : 
 # Skips invalid input. 
 continue 
 if 
 user_data 
 . 
 user_identifiers 
 : 
 # Adds an AudienceMember with the formatted and hashed identifiers. 
 audience_member 
 : 
 datamanager_v1 
 . 
 AudienceMember 
 = 
 ( 
 datamanager_v1 
 . 
 AudienceMember 
 () 
 ) 
 audience_member 
 . 
 user_data 
 = 
 user_data 
 audience_members 
 . 
 append 
 ( 
 audience_member 
 ) 
 # Configures the destination. 
 destination 
 : 
 datamanager_v1 
 . 
 Destination 
 = 
 datamanager_v1 
 . 
 Destination 
 () 
 destination 
 . 
 operating_account 
 . 
 account_type 
 = 
 operating_account_type 
 destination 
 . 
 operating_account 
 . 
 account_id 
 = 
 operating_account_id 
 if 
 login_account_type 
 or 
 login_account_id 
 : 
 destination 
 . 
 login_account 
 . 
 account_type 
 = 
 login_account_type 
 destination 
 . 
 login_account 
 . 
 account_id 
 = 
 login_account_id 
 if 
 linked_account_type 
 or 
 linked_account_id 
 : 
 destination 
 . 
 linked_account 
 . 
 account_type 
 = 
 linked_account_type 
 destination 
 . 
 linked_account 
 . 
 account_id 
 = 
 linked_account_id 
 destination 
 . 
 product_destination_id 
 = 
 audience_id 
 # Configures the EncryptionInfo for the request if encryption parameters provided. 
 if 
 use_encryption 
 : 
 encryption_info 
 : 
 datamanager_v1 
 . 
 EncryptionInfo 
 = 
 ( 
 datamanager_v1 
 . 
 EncryptionInfo 
 ( 
 gcp_wrapped_key_info 
 = 
 datamanager_v1 
 . 
 GcpWrappedKeyInfo 
 ( 
 kek_uri 
 = 
 key_uri 
 , 
 wip_provider 
 = 
 wip_provider 
 , 
 key_type 
 = 
 datamanager_v1 
 . 
 GcpWrappedKeyInfo 
 . 
 KeyType 
 . 
 XCHACHA20_POLY1305 
 , 
 # Sets the encrypted_dek field to the Base64-encoded encrypted DEK. 
 encrypted_dek 
 = 
 formatter 
 . 
 base64_encode 
 ( 
 encrypter 
 . 
 encrypted_dek_bytes 
 ), 
 ) 
 ) 
 ) 
 # Creates a client for the ingestion service. 
 client 
 : 
 datamanager_v1 
 . 
 IngestionServiceClient 
 = 
 ( 
 datamanager_v1 
 . 
 IngestionServiceClient 
 () 
 ) 
 # Batches requests to send up to the maximum number of audience members per 
 # request. 
 request_count 
 = 
 0 
 for 
 i 
 in 
 range 
 ( 
 0 
 , 
 len 
 ( 
 audience_members 
 ), 
 _MAX_MEMBERS_PER_REQUEST 
 ): 
 request_count 
 += 
 1 
 audience_members_batch 
 = 
 audience_members 
 [ 
 i 
 : 
 i 
 + 
 _MAX_MEMBERS_PER_REQUEST 
 ] 
 # Sends the request. 
 request 
 : 
 datamanager_v1 
 . 
 IngestAudienceMembersRequest 
 = 
 ( 
 datamanager_v1 
 . 
 IngestAudienceMembersRequest 
 ( 
 destinations 
 = 
 [ 
 destination 
 ], 
 # Adds members from the current batch. 
 audience_members 
 = 
 audience_members_batch 
 , 
 consent 
 = 
 datamanager_v1 
 . 
 Consent 
 ( 
 ad_user_data 
 = 
 datamanager_v1 
 . 
 ConsentStatus 
 . 
 CONSENT_GRANTED 
 , 
 ad_personalization 
 = 
 datamanager_v1 
 . 
 ConsentStatus 
 . 
 CONSENT_GRANTED 
 , 
 ), 
 terms_of_service 
 = 
 datamanager_v1 
 . 
 TermsOfService 
 ( 
 customer_match_terms_of_service_status 
 = 
 datamanager_v1 
 . 
 TermsOfServiceStatus 
 . 
 ACCEPTED 
 , 
 ), 
 # Sets encoding to match the encoding used. 
 encoding 
 = 
 datamanager_v1 
 . 
 Encoding 
 . 
 HEX 
 , 
 # Sets validate_only. If true, then the Data Manager API only 
 # validates the request but doesn't apply changes. 
 validate_only 
 = 
 validate_only 
 , 
 ) 
 ) 
 if 
 use_encryption 
 : 
 # Sets encryption info on the request. 
 request 
 . 
 encryption_info 
 = 
 encryption_info 
 # Sends the request. 
 response 
 : 
 datamanager_v1 
 . 
 IngestAudienceMembersResponse 
 = 
 ( 
 client 
 . 
 ingest_audience_members 
 ( 
 request 
 = 
 request 
 ) 
 ) 
 # Logs the response. 
 _logger 
 . 
 info 
 ( 
 "Response for request # 
 %d 
 : 
 \n 
 %s 
 " 
 , 
 request_count 
 , 
 response 
 ) 
 _logger 
 . 
 info 
 ( 
 "# of requests sent: 
 %d 
 " 
 , 
 request_count 
 ) 
 def 
  
 read_member_data_file 
 ( 
 csv_file 
 : 
 str 
 ) 
 - 
> List 
 [ 
 Dict 
 [ 
 str 
 , 
 str 
 ]]: 
  
 """Reads the comma-separated member data file. 
 Args: 
 csv_file: the member data file. Expected format is one comma-separated row 
 per audience member, with a header row containing headers of the form 
 "email_..." or "phone_...". 
 """ 
 members 
 = 
 [] 
 with 
 open 
 ( 
 csv_file 
 , 
 "r" 
 ) 
 as 
 f 
 : 
 reader 
 = 
 csv 
 . 
 DictReader 
 ( 
 f 
 . 
 readlines 
 ()) 
 line_num 
 = 
 0 
 for 
 member_row 
 in 
 reader 
 : 
 line_num 
 += 
 1 
 member 
 = 
 { 
 "emails" 
 : 
 [], 
 "phone_numbers" 
 : 
 [], 
 } 
 for 
 field_name 
 , 
 field_value 
 in 
 member_row 
 . 
 items 
 (): 
 if 
 not 
 field_name 
 : 
 # Ignores trailing field without a corresponding header. 
 continue 
 field_value 
 = 
 field_value 
 . 
 strip 
 () 
 if 
 len 
 ( 
 field_value 
 ) 
 == 
 0 
 : 
 # Ignores blank/empty value. 
 continue 
 if 
 field_name 
 . 
 startswith 
 ( 
 "email_" 
 ): 
 member 
 [ 
 "emails" 
 ] 
 . 
 append 
 ( 
 field_value 
 ) 
 elif 
 field_name 
 . 
 startswith 
 ( 
 "phone_" 
 ): 
 member 
 [ 
 "phone_numbers" 
 ] 
 . 
 append 
 ( 
 field_value 
 ) 
 else 
 : 
 _logger 
 . 
 warning 
 ( 
 "Ignoring unrecognized field: 
 %s 
 " 
 , 
 field_name 
 ) 
 if 
 member 
 [ 
 "emails" 
 ] 
 or 
 member 
 [ 
 "phone_numbers" 
 ]: 
 members 
 . 
 append 
 ( 
 member 
 ) 
 else 
 : 
 _logger 
 . 
 warning 
 ( 
 "Ignoring line # 
 %d 
 . No data." 
 , 
 line_num 
 ) 
 return 
 members 
 if 
 __name__ 
 == 
 "__main__" 
 : 
 # Configures logging. 
 logging 
 . 
 basicConfig 
 ( 
 level 
 = 
 logging 
 . 
 INFO 
 ) 
 parser 
 = 
 argparse 
 . 
 ArgumentParser 
 ( 
 description 
 = 
 ( 
 "Sends audience members from a CSV file to a destination." 
 ), 
 fromfile_prefix_chars 
 = 
 "@" 
 , 
 ) 
 # The following argument(s) should be provided to run the example. 
 parser 
 . 
 add_argument 
 ( 
 "--operating_account_type" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 True 
 , 
 help 
 = 
 "The account type of the operating account." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--operating_account_id" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 True 
 , 
 help 
 = 
 "The ID of the operating account." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--login_account_type" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 False 
 , 
 help 
 = 
 "The account type of the login account." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--login_account_id" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 False 
 , 
 help 
 = 
 "The ID of the login account." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--linked_account_type" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 False 
 , 
 help 
 = 
 "The account type of the linked account." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--linked_account_id" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 False 
 , 
 help 
 = 
 "The ID of the linked account." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--audience_id" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 True 
 , 
 help 
 = 
 "The ID of the destination audience." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--csv_file" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 True 
 , 
 help 
 = 
 "Comma-separated file containing user data to ingest." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--key_uri" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 False 
 , 
 help 
 = 
 "URI of the Google Cloud KMS key for encrypting data. If this parameter is set, you " 
 + 
 "must also set the --wip_provider parameter." 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--wip_provider" 
 , 
 type 
 = 
 str 
 , 
 required 
 = 
 False 
 , 
 help 
 = 
 "Workload Identity Pool provider name for encrypting data. If this parameter is set, " 
 + 
 "you must also set the --key_uri parameter. The argument for this parameter must follow " 
 + 
 "the pattern: " 
 + 
 "projects/PROJECT_ID/locations/global/workloadIdentityPools/WIP_ID/providers/PROVIDER_ID" 
 , 
 ) 
 parser 
 . 
 add_argument 
 ( 
 "--validate_only" 
 , 
 choices 
 = 
 [ 
 "true" 
 , 
 "false" 
 ], 
 default 
 = 
 "true" 
 , 
 help 
 = 
 "Whether to enable validate_only on the request. Must be 'true' or 'false'. " 
 + 
 "Defaults to 'true'." 
 , 
 ) 
 args 
 = 
 parser 
 . 
 parse_args 
 () 
 main 
 ( 
 args 
 . 
 operating_account_type 
 , 
 args 
 . 
 operating_account_id 
 , 
 args 
 . 
 audience_id 
 , 
 args 
 . 
 csv_file 
 , 
 args 
 . 
 validate_only 
 == 
 "true" 
 , 
 args 
 . 
 login_account_type 
 , 
 args 
 . 
 login_account_id 
 , 
 args 
 . 
 linked_account_type 
 , 
 args 
 . 
 linked_account_id 
 , 
 args 
 . 
 key_uri 
 , 
 args 
 . 
 wip_provider 
 , 
 ) 
  

Success responses

A successful request returns a response with an object containing a requestId .

  { 
  
 "requestId" 
 : 
  
 "126365e1-16d0-4c81-9de9-f362711e250a" 
 } 
 

Record the requestId returned so you can retrieve diagnostics as each destination in the request is processed.

Failure responses

A failed request results in an error response status code such as 400 Bad Request , and a response with error details.

For example, an emailAddress containing a plain text string instead of a hex encoded value produces the following response:

  { 
  
 "error" 
 : 
  
 { 
  
 "code" 
 : 
  
 400 
 , 
  
 "message" 
 : 
  
 "There was a problem with the request." 
 , 
  
 "status" 
 : 
  
 "INVALID_ARGUMENT" 
 , 
  
 "details" 
 : 
  
 [ 
  
 { 
  
 "@type" 
 : 
  
 "type.googleapis.com/google.rpc.ErrorInfo" 
 , 
  
 "reason" 
 : 
  
 "INVALID_ARGUMENT" 
 , 
  
 "domain" 
 : 
  
 "datamanager.googleapis.com" 
  
 }, 
  
 { 
  
 "@type" 
 : 
  
 "type.googleapis.com/google.rpc.BadRequest" 
 , 
  
 "fieldViolations" 
 : 
  
 [ 
  
 { 
  
 "field" 
 : 
  
 "audience_members.audience_members[0].user_data.user_identifiers" 
 , 
  
 "description" 
 : 
  
 "Email is not hex encoded." 
 , 
  
 "reason" 
 : 
  
 "INVALID_HEX_ENCODING" 
  
 } 
  
 ] 
  
 } 
  
 ] 
  
 } 
 } 
 

An emailAddress that isn't hashed and is only hex encoded produces the following response:

  { 
  
 "error" 
 : 
  
 { 
  
 "code" 
 : 
  
 400 
 , 
  
 "message" 
 : 
  
 "There was a problem with the request." 
 , 
  
 "status" 
 : 
  
 "INVALID_ARGUMENT" 
 , 
  
 "details" 
 : 
  
 [ 
  
 { 
  
 "@type" 
 : 
  
 "type.googleapis.com/google.rpc.ErrorInfo" 
 , 
  
 "reason" 
 : 
  
 "INVALID_ARGUMENT" 
 , 
  
 "domain" 
 : 
  
 "datamanager.googleapis.com" 
  
 }, 
  
 { 
  
 "@type" 
 : 
  
 "type.googleapis.com/google.rpc.BadRequest" 
 , 
  
 "fieldViolations" 
 : 
  
 [ 
  
 { 
  
 "field" 
 : 
  
 "audience_members.audience_members[0]" 
 , 
  
 "reason" 
 : 
  
 "INVALID_SHA256_FORMAT" 
  
 } 
  
 ] 
  
 } 
  
 ] 
  
 } 
 } 
 

Send events for multiple destinations

If your data contains audience members for different destinations, you can send them in the same request by using destination references. See Limits and quotas for the maximum number of destinations per request.

For example, if you have an audience member for user list ID 11112222 and another audience member for user list ID 77778888 , send both audience members in a single request by setting the reference of each Destination . The reference is user-defined—the only requirement is that each Destination has a unique reference . Here's the modified destinations list for the request:

   
 "destinations" 
 : 
  
 [ 
  
 { 
  
 "operatingAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " OPERATING_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " OPERATING_ACCOUNT_ID 
" 
  
 }, 
  
 "loginAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " LOGIN_ACCOUNT_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " LOGIN_ACCOUNT_ID 
" 
  
 }, 
  
 "productDestinationId" 
 : 
  
 "11112222" 
 , 
  
 "reference" 
 : 
  
 "audience_1" 
  
 }, 
  
 { 
  
 "operatingAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " OPERATING_ACCOUNT_2_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " OPERATING_ACCOUNT_2_ID 
" 
  
 }, 
  
 "loginAccount" 
 : 
  
 { 
  
 "accountType" 
 : 
  
 " LOGIN_ACCOUNT_2_TYPE 
" 
 , 
  
 "accountId" 
 : 
  
 " LOGIN_ACCOUNT_2_ID 
" 
  
 }, 
  
 "productDestinationId" 
 : 
  
 "77778888" 
 , 
  
 "reference" 
 : 
  
 "audience_2" 
  
 } 
  
 ] 
 

Set the destination_references of each AudienceMember to send it to one or more specific destinations. For example, here's an AudienceMember that's only for the first Destination , so its destination_references list only contains the reference of the first Destination :

  { 
  
 "userData" 
 : 
  
 { 
  
 "userIdentifiers" 
 : 
  
 [ 
  
 { 
  
 "emailAddress" 
 : 
  
 "07e2f1394b0ea80e2adca010ea8318df697001a005ba7452720edda4b0ce57b3" 
  
 }, 
  
 { 
  
 "emailAddress" 
 : 
  
 "1df6b43bc68dd38eca94e6a65b4f466ae537b796c81a526918b40ac4a7b906c7" 
  
 } 
  
 ], 
  
 } 
  
 "destinationReferences" 
 : 
  
 [ 
  
 "audience_1" 
  
 ] 
 } 
 

The destination_references field is a list, so you can specify multiple destinations for an audience member. If you don't set the destination_references of an AudienceMember , the Data Manager API sends the audience member to all of the destinations in the request.

Next steps

Design a Mobile Site
View Site in Mobile | Classic
Share by: