Bạn có thể làm theo hướng dẫn bắt đầu nhanh này để làm quen với Data Manager API. Chọn phiên bản hướng dẫn bắt đầu nhanh mà bạn muốn xem:
Trong hướng dẫn bắt đầu nhanh này, bạn sẽ hoàn tất các bước sau:
- Chuẩn bị một
Destinationđể nhận dữ liệu về đối tượng. - Chuẩn bị dữ liệu đối tượng để gửi.
- Tạo yêu cầu
IngestionServicecho các thành viên trong đối tượng. - Gửi yêu cầu bằng Google APIs Explorer.
- Tìm hiểu về các phản hồi thành công và không thành công.
Chuẩn bị cho một điểm đến
Trước khi có thể gửi dữ liệu, bạn cần chuẩn bị đích đến để gửi dữ liệu. Sau đây là một Destination mẫu để bạn sử dụng. Hãy xem phần Định cấu hình đích đến để biết ví dụ về đích đến cho nhiều trường hợp.
{
"operatingAccount": {
"accountType": "OPERATING_ACCOUNT_TYPE",
"accountId": "OPERATING_ACCOUNT_ID"
},
"loginAccount": {
"accountType": "LOGIN_ACCOUNT_TYPE",
"accountId": "LOGIN_ACCOUNT_ID"
},
"productDestinationId": "AUDIENCE_ID"
}
- Đặt
operatingAccountthành loại tài khoản và mã tài khoản sẽ nhận dữ liệu đối tượng. - Nếu thông tin xác thực OAuth của bạn là dành cho người dùng có quyền truy cập vào tài khoản người quản lý Google Ads, trong đó có
operatingAccountlà một trong các tài khoản phụ, hãy đặtloginAccountthành loại tài khoản và mã nhận dạng của tài khoản người quản lý. - Nếu thông tin đăng nhập OAuth dành cho người dùng có quyền truy cập trực tiếp vào
operatingAccount, thì bạn không cần đặtloginAccount.
Chuẩn bị dữ liệu về đối tượng
Hãy xem xét dữ liệu mẫu sau đây trong một tệp được phân tách bằng dấu phẩy. Mỗi dòng trong tệp tương ứng với một thành viên của đối tượng và mỗi thành viên có tối đa 3 địa chỉ email.
#,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,
Địa chỉ email có các yêu cầu sau về định dạng và băm:
- Xoá tất cả khoảng trắng ở đầu, cuối và giữa.
- Chuyển đổi địa chỉ email thành chữ thường.
- Băm địa chỉ email bằng thuật toán SHA-256.
- Mã hoá các byte băm bằng hệ thập lục phân (hex) hoặc phương thức mã hoá Base64. Các ví dụ trong hướng dẫn này sử dụng phương thức mã hoá thập lục phân.
Sau đây là dữ liệu đã được định dạng:
#,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,
Sau đây là dữ liệu sau khi được băm và mã hoá:
#,email_1,email_2,email_3
1,07e2f1394b0ea80e2adca010ea8318df697001a005ba7452720edda4b0ce57b3,1df6b43bc68dd38eca94e6a65b4f466ae537b796c81a526918b40ac4a7b906c7
2,2ef46c4214c3fc1b277a2d976d55194e12b899aa50d721f28da858c7689756e3,54e410b14fa652a4b49b43aff6eaf92ad680d4d1e5e62ed71b86cd3188385a51,e8bd3f8da6f5af73bec1ab3fbf7beb47482c4766dfdfc94e6bd89e359c139478
3,05bb62526f091b45d20e243d194766cca8869137421047dc53fa4876d111a6f0,f1fcde379f31f4d446b76ee8f34860eca2288adc6b6d6c0fdc56d9eee75a2fa5
4,83a834cc5327bc4dee7c5408988040dc5813c7662611cd93b707aff72bf7d33f,223ebda6f6889b1494551ba902d9d381daf2f642bae055888e96343d53e9f9c4
Dưới đây là một mẫu AudienceMember cho địa chỉ email đã được định dạng, băm và mã hoá của dana@example.com và danam@example.com từ hàng đầu tiên của dữ liệu đầu vào:
{
"userData": {
"userIdentifiers": [
{
"emailAddress": "07e2f1394b0ea80e2adca010ea8318df697001a005ba7452720edda4b0ce57b3"
},
{
"emailAddress": "1df6b43bc68dd38eca94e6a65b4f466ae537b796c81a526918b40ac4a7b906c7"
}
]
}
}
Tạo nội dung yêu cầu
Để tạo nội dung yêu cầu, hãy kết hợp destinations và audienceMembers, đặt trường encoding và thêm mọi trường yêu cầu khác mà bạn muốn đưa vào, chẳng hạn như validateOnly và consent.
Nếu gửi thành viên đối tượng cho tính năng So khớp khách hàng, hãy đặt termsOfService để cho biết liệu người dùng đã chấp nhận điều khoản dịch vụ của tính năng So khớp khách hàng hay chưa.
Các ví dụ trong hướng dẫn này không sử dụng phương thức mã hoá, nhưng bạn có thể làm theo hướng dẫn trong phần Mã hoá dữ liệu người dùng để thêm phương thức mã hoá vào quy trình của mình.
Gửi yêu cầu
Sau đây là các bước để thử một yêu cầu từ trình duyệt:
- Chọn thẻ REST rồi nhấp vào Open in API Explorer (Mở trong Trình khám phá API) để mở Trình khám phá API trong một thẻ hoặc cửa sổ mới.
- Trong phần nội dung yêu cầu trong API Explorer, hãy thay thế từng chuỗi bắt đầu bằng
REPLACE_WITH, chẳng hạn nhưREPLACE_WITH_OPERATING_ACCOUNT_TYPE, bằng giá trị có liên quan. - Nhấp vào Thực thi ở cuối trang API Explorer và hoàn tất các lời nhắc uỷ quyền để gửi yêu cầu.
- Đặt
validateOnlythànhtrueđể xác thực yêu cầu mà không áp dụng các thay đổi. Khi bạn đã sẵn sàng áp dụng các thay đổi, hãy đặtvalidateOnlythànhfalse.
Nếu bạn đã cài đặt một thư viện ứng dụng, hãy chọn thẻ cho ngôn ngữ lập trình bạn đã chọn để xem mẫu mã hoàn chỉnh về cách tạo và gửi yêu cầu.
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; } }
Nút
#!/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, )
Phản hồi thành công
Yêu cầu thành công sẽ trả về một phản hồi có chứa một đối tượng requestId.
{
"requestId": "126365e1-16d0-4c81-9de9-f362711e250a"
}
Ghi lại requestId được trả về để bạn có thể truy xuất thông tin chẩn đoán khi mỗi đích đến trong yêu cầu được xử lý.
Phản hồi thất bại
Yêu cầu không thành công sẽ dẫn đến mã trạng thái phản hồi lỗi, chẳng hạn như 400 Bad
Request và phản hồi có thông tin chi tiết về lỗi.
Ví dụ: emailAddress chứa một chuỗi văn bản thuần tuý thay vì một giá trị được mã hoá hex sẽ tạo ra phản hồi sau:
{
"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"
}
]
}
]
}
}
Một emailAddress không được băm và chỉ được mã hoá theo hệ thập lục phân sẽ tạo ra phản hồi sau:
{
"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"
}
]
}
]
}
}
Gửi sự kiện cho nhiều đích đến
Nếu dữ liệu của bạn chứa các thành viên đối tượng cho nhiều đích đến, bạn có thể gửi họ trong cùng một yêu cầu bằng cách sử dụng thông tin tham chiếu về đích đến.
Ví dụ: nếu bạn có một thành viên đối tượng cho mã danh sách người dùng 11112222 và một thành viên đối tượng khác cho mã danh sách người dùng 77778888, hãy gửi cả hai thành viên đối tượng trong một yêu cầu bằng cách đặt reference của mỗi Destination. reference do người dùng xác định – yêu cầu duy nhất là mỗi Destination phải có một reference duy nhất. Sau đây là danh sách destinations đã sửa đổi cho yêu cầu:
"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"
}
]
Đặt destination_references của mỗi AudienceMember để gửi đến một hoặc nhiều đích đến cụ thể. Ví dụ: sau đây là một AudienceMember chỉ dành cho Destination đầu tiên, vì vậy, danh sách destination_references của AudienceMember này chỉ chứa reference của Destination đầu tiên:
{
"userData": {
"userIdentifiers": [
{
"emailAddress": "07e2f1394b0ea80e2adca010ea8318df697001a005ba7452720edda4b0ce57b3"
},
{
"emailAddress": "1df6b43bc68dd38eca94e6a65b4f466ae537b796c81a526918b40ac4a7b906c7"
}
],
}
"destinationReferences": [
"audience_1"
]
}
Trường destination_references là một danh sách, vì vậy bạn có thể chỉ định nhiều đích đến cho một thành viên trong đối tượng. Nếu bạn không đặt destination_references của AudienceMember, Data Manager API sẽ gửi thành viên đối tượng đến tất cả các đích đến trong yêu cầu.
Các bước tiếp theo
- Định cấu hình quy trình xác thực và thiết lập môi trường bằng một thư viện ứng dụng.
- Tìm hiểu về các yêu cầu về định dạng, băm và mã hoá đối với từng loại dữ liệu.
- Tìm hiểu cách mã hoá dữ liệu người dùng.
- Tìm hiểu cách truy xuất thông tin chẩn đoán cho các yêu cầu của bạn.
- Tìm hiểu về các phương pháp hay nhất.
- Tìm hiểu về hạn mức và cuota.