7 added
9 removed
Original
2026-01-01
Modified
2026-03-10
1
<p><em>Solution Recipes are tutorials to achieve specific objectives in Klaviyo. They can also help you master Klaviyo, learn new third-party technologies, and come up with creative ideas. They are written mainly for developers and technically-advanced users.</em></p>
1
<p><em>Solution Recipes are tutorials to achieve specific objectives in Klaviyo. They can also help you master Klaviyo, learn new third-party technologies, and come up with creative ideas. They are written mainly for developers and technically-advanced users.</em></p>
2
<p><em>This is a guest post written by Shelly Saturné, who recently built this Solution Recipe as part of Klaviyo’s Solution Architect Mentor Program.</em></p>
2
<p><em>This is a guest post written by Shelly Saturné, who recently built this Solution Recipe as part of Klaviyo’s Solution Architect Mentor Program.</em></p>
3
<p>What you’ll learn</p>
3
<p>What you’ll learn</p>
4
-
<p>How the Profiles, Events, and Metrics API endpoints are required to build a log history file of subscription and suppression events</p>
4
+
<p>How the Profiles, Events, and Metrics API endpoints are required to build a log history file of subscription and suppression eventsHow to run the Python script to generate a .csv file that contains all historical subscribed and suppressed data for every profile in a given account</p>
5
-
<p>How to run the Python script to generate a .csv file that contains all historical subscribed and suppressed data for every profile in a given account</p>
6
<p>Why it matters</p>
5
<p>Why it matters</p>
7
-
<p>Although profiles are tracked with the subscribe and unsubscribe events and tagged accordingly when suppressed in Klaviyo, there isn’t a dedicated space in the platform that provides a full view of every subscription and suppression instance for a particular profile. This is what this Solution Recipe aims to solve.</p>
6
+
<p>Although profiles are tracked with the subscribe and unsubscribe events and tagged accordingly when suppressed in Klaviyo, there isn’t a dedicated space in the platform that provides a full view of every subscription and suppression instance for a particular profile. This is what this Solution Recipe aims to solve.This Solution Recipe will show Klaviyo users how to obtain a more holistic view of subscription and suppression status and events for a single profile in a centralized place.</p>
8
-
<p>This Solution Recipe will show Klaviyo users how to obtain a more holistic view of subscription and suppression status and events for a single profile in a centralized place.</p>
9
<p>Level of sophistication</p>
7
<p>Level of sophistication</p>
10
<p>Moderate</p>
8
<p>Moderate</p>
11
<h2>Introduction</h2>
9
<h2>Introduction</h2>
12
<p>On a day-to-day basis, Klaviyo strives to improve the experience of those using the platform. One way to do that is to provide Klaviyo users with accurate and timely information regarding a customer profile’s subscription and suppression status.</p>
10
<p>On a day-to-day basis, Klaviyo strives to improve the experience of those using the platform. One way to do that is to provide Klaviyo users with accurate and timely information regarding a customer profile’s subscription and suppression status.</p>
13
<p>Today, when you navigate through the platform to view a customer profile, you find the following subscription details for both email and SMS:</p>
11
<p>Today, when you navigate through the platform to view a customer profile, you find the following subscription details for both email and SMS:</p>
14
<ul><li>Status</li>
12
<ul><li>Status</li>
15
<li>Method</li>
13
<li>Method</li>
16
<li>Date</li>
14
<li>Date</li>
17
<li>Added by</li>
15
<li>Added by</li>
18
</ul><p>When a customer profile is suppressed, their profile page includes additional details:</p>
16
</ul><p>When a customer profile is suppressed, their profile page includes additional details:</p>
19
<ul><li>Red suppression tag</li>
17
<ul><li>Red suppression tag</li>
20
<li>Suppression details</li>
18
<li>Suppression details</li>
21
</ul><p>Although this subscription details box is useful and accessible, it does not provide a timeline of all subscription and suppression events. The details box only shows the most current information relating to a profile’s subscription status.</p>
19
</ul><p>Although this subscription details box is useful and accessible, it does not provide a timeline of all subscription and suppression events. The details box only shows the most current information relating to a profile’s subscription status.</p>
22
<p>Klaviyo users want a more holistic view of subscription status, including more detail about how a profile was suppressed, when and how it was unsuppressed, and more. They want access to all subscribe and suppress updates for a single profile in a centralized place.</p>
20
<p>Klaviyo users want a more holistic view of subscription status, including more detail about how a profile was suppressed, when and how it was unsuppressed, and more. They want access to all subscribe and suppress updates for a single profile in a centralized place.</p>
23
<p>This does not currently exist in the platform. There’s no log or historic page for subscription changes. Oftentimes, the Klaviyo success team must piece together details to provide Klaviyo users with the holistic story of how and when a profile was suppressed and later unsuppressed. That’s time the success team could be spending in a more productive way to add value to Klaviyo users.</p>
21
<p>This does not currently exist in the platform. There’s no log or historic page for subscription changes. Oftentimes, the Klaviyo success team must piece together details to provide Klaviyo users with the holistic story of how and when a profile was suppressed and later unsuppressed. That’s time the success team could be spending in a more productive way to add value to Klaviyo users.</p>
24
<p>Over the past 3 years, the number of business cases related to suppression and unsubscribe has increased drastically:</p>
22
<p>Over the past 3 years, the number of business cases related to suppression and unsubscribe has increased drastically:</p>
25
<ul><li>2021: 0.12% suppression tickets; 0.06% unsubscribe tickets</li>
23
<ul><li>2021: 0.12% suppression tickets; 0.06% unsubscribe tickets</li>
26
<li>2022: 0.12% suppression tickets; 0.05% unsubscribe tickets</li>
24
<li>2022: 0.12% suppression tickets; 0.05% unsubscribe tickets</li>
27
<li>2023: 0.40% suppression tickets; 0.18% unsubscribe tickets</li>
25
<li>2023: 0.40% suppression tickets; 0.18% unsubscribe tickets</li>
28
</ul><p>Clearly, Klaviyo users want a better understanding of the timeline of subscription and suppression events.</p>
26
</ul><p>Clearly, Klaviyo users want a better understanding of the timeline of subscription and suppression events.</p>
29
<h2>Challenge</h2>
27
<h2>Challenge</h2>
30
<p>To extract the subscription and suppression data as it is tracked on a profile, and display the data in a format that is human-readable and digestible.</p>
28
<p>To extract the subscription and suppression data as it is tracked on a profile, and display the data in a format that is human-readable and digestible.</p>
31
<h2>Ingredients</h2>
29
<h2>Ingredients</h2>
32
<ul><li>Klaviyo API</li>
30
<ul><li>Klaviyo API</li>
33
<li>Python IDE</li>
31
<li>Python IDE</li>
34
<li>Postman</li>
32
<li>Postman</li>
35
<li>A Klaviyo account</li>
33
<li>A Klaviyo account</li>
36
<li>Private API key</li>
34
<li>Private API key</li>
37
</ul><p>After reviewing the Klaviyo API, we selected the following endpoints to extract the necessary information:</p>
35
</ul><p>After reviewing the Klaviyo API, we selected the following endpoints to extract the necessary information:</p>
38
-
<ul><li>Profiles API<ul><li>GET<a>https://a.klaviyo.com/api/profiles/</a></li>
36
+
<ul><li>Profiles APIGET<a>https://a.klaviyo.com/api/profiles/</a>Functions in the script to extract data from Profiles endpoint, manipulate the data to filter and return profile id, consent, updated date<ul><li>GET<a>https://a.klaviyo.com/api/profiles/</a></li>
39
<li>Functions in the script to extract data from Profiles endpoint, manipulate the data to filter and return profile id, consent, updated date</li>
37
<li>Functions in the script to extract data from Profiles endpoint, manipulate the data to filter and return profile id, consent, updated date</li>
40
</ul></li>
38
</ul></li>
41
-
<li>Events API<ul><li>GET<a>https://a.klaviyo.com/api/events/</a></li>
39
+
<li>Events APIGET<a>https://a.klaviyo.com/api/events/</a>A series of functions pulls metric events from profiles and manipulates the data to extract profile details<ul><li>GET<a>https://a.klaviyo.com/api/events/</a></li>
42
<li>A series of functions pulls metric events from profiles and manipulates the data to extract profile details</li>
40
<li>A series of functions pulls metric events from profiles and manipulates the data to extract profile details</li>
43
</ul></li>
41
</ul></li>
44
-
<li>Metrics API<ul><li>GET<a>https://a.klaviyo.com/api/metrics/</a></li>
42
+
<li>Metrics APIGET<a>https://a.klaviyo.com/api/metrics/</a>Multiple metric functions pull all metric and query for event-related data for a specified metricSave as a .csvAll filtered data from the API are saved into separate .csv files, then merged into a single .csv fileOpen .csv in Numbers applicationCode-generated .csv file is viewable in the Numbers application through a simple right click on the file<ul><li>GET<a>https://a.klaviyo.com/api/metrics/</a></li>
45
-
<li>Multiple metric functions pull all metric and query for event-related data for a specified metric<ul><li>Save as a .csv<ul><li>All filtered data from the API are saved into separate .csv files, then merged into a single .csv file</li>
43
+
<li>Multiple metric functions pull all metric and query for event-related data for a specified metricSave as a .csvAll filtered data from the API are saved into separate .csv files, then merged into a single .csv fileOpen .csv in Numbers applicationCode-generated .csv file is viewable in the Numbers application through a simple right click on the file<ul><li>Save as a .csvAll filtered data from the API are saved into separate .csv files, then merged into a single .csv file<ul><li>All filtered data from the API are saved into separate .csv files, then merged into a single .csv file</li>
46
</ul></li>
44
</ul></li>
47
-
<li>Open .csv in Numbers application<ul><li>Code-generated .csv file is viewable in the Numbers application through a simple right click on the file</li>
45
+
<li>Open .csv in Numbers applicationCode-generated .csv file is viewable in the Numbers application through a simple right click on the file<ul><li>Code-generated .csv file is viewable in the Numbers application through a simple right click on the file</li>
48
</ul></li>
46
</ul></li>
49
</ul></li>
47
</ul></li>
50
</ul></li>
48
</ul></li>
51
</ul><h2>Instructions</h2>
49
</ul><h2>Instructions</h2>
52
<h3>Step 1</h3>
50
<h3>Step 1</h3>
53
<p>Utilize the Profiles endpoint to render JSON data that contains the profile id, consent, updated date:</p>
51
<p>Utilize the Profiles endpoint to render JSON data that contains the profile id, consent, updated date:</p>
54
def get_profiles(): baseurl = "https://a.klaviyo.com/api/profiles/?page[size]=100" headers = { "accept": "application/json", "revision": "2023-02-22", "Authorization": f"Klaviyo-API-Key {os.getenv('KLAVIYO_PRIVATE_KEY')}" } response = requests.get(baseurl, headers=headers) response_data = response.json()["data"] next_url = response.json()["links"].get("next", "") counter = 1 while next_url: counter += 1 print(f"processing page {counter}") response = requests.get(next_url, headers=headers) response_data += response.json()["data"] next_url = response.json()["links"].get("next", "") return response_data<h3>Step 2</h3>
52
def get_profiles(): baseurl = "https://a.klaviyo.com/api/profiles/?page[size]=100" headers = { "accept": "application/json", "revision": "2023-02-22", "Authorization": f"Klaviyo-API-Key {os.getenv('KLAVIYO_PRIVATE_KEY')}" } response = requests.get(baseurl, headers=headers) response_data = response.json()["data"] next_url = response.json()["links"].get("next", "") counter = 1 while next_url: counter += 1 print(f"processing page {counter}") response = requests.get(next_url, headers=headers) response_data += response.json()["data"] next_url = response.json()["links"].get("next", "") return response_data<h3>Step 2</h3>
55
<p>Step through the JSON data to extract the profile id, consent, and updated date, and add all of the data into an empty list:</p>
53
<p>Step through the JSON data to extract the profile id, consent, and updated date, and add all of the data into an empty list:</p>
56
def process_profile_data(retrieve_profile_data): filtered_profile_data = [] for retrieve_profile in retrieve_profile_data: filtered_profile_data.append({ "profile_id": retrieve_profile["id"], "subscription_updated_date": retrieve_profile.get("attributes", {}).get("subscriptions", {}).get("email",{}).get("marketing", {}).get("timestamp", ""), "profile_email_consent": retrieve_profile.get("attributes", {}).get("subscriptions", {}).get("email", {}).get("marketing", {}).get("consent", ""), "included_profile_firstname": retrieve_profile.get("attributes", {}).get("first_name", {}), "included_profile_email": retrieve_profile.get("attributes", {}).get("email", {}) }) return filtered_profile_data<h3>Step 3</h3>
54
def process_profile_data(retrieve_profile_data): filtered_profile_data = [] for retrieve_profile in retrieve_profile_data: filtered_profile_data.append({ "profile_id": retrieve_profile["id"], "subscription_updated_date": retrieve_profile.get("attributes", {}).get("subscriptions", {}).get("email",{}).get("marketing", {}).get("timestamp", ""), "profile_email_consent": retrieve_profile.get("attributes", {}).get("subscriptions", {}).get("email", {}).get("marketing", {}).get("consent", ""), "included_profile_firstname": retrieve_profile.get("attributes", {}).get("first_name", {}), "included_profile_email": retrieve_profile.get("attributes", {}).get("email", {}) }) return filtered_profile_data<h3>Step 3</h3>
57
<p>Store the profile data from the above function into a new .csv file:</p>
55
<p>Store the profile data from the above function into a new .csv file:</p>
58
def save_filtered_profile_data_as_csv(filtered_profile_subscription_data): with open('subscription_events_profile_data.csv', 'w') as f: writer = csv.writer(f) writer.writerow(filtered_profile_subscription_data[0].keys()) for filtered_profile in filtered_profile_subscription_data: writer.writerow(filtered_profile.values())<h3>Step 4</h3>
56
def save_filtered_profile_data_as_csv(filtered_profile_subscription_data): with open('subscription_events_profile_data.csv', 'w') as f: writer = csv.writer(f) writer.writerow(filtered_profile_subscription_data[0].keys()) for filtered_profile in filtered_profile_subscription_data: writer.writerow(filtered_profile.values())<h3>Step 4</h3>
59
<p>Get every metric in the account using the Metrics endpoint:</p>
57
<p>Get every metric in the account using the Metrics endpoint:</p>
60
def get_metric_data(): url = "https://a.klaviyo.com/api/metrics" headers = { "accept": "application/json", "revision": "2023-02-22", "Authorization": f"Klaviyo-API-Key {os.getenv('KLAVIYO_PRIVATE_KEY')}" } response = requests.get(url, headers=headers) return response.json()["data"]<h3>Step 5</h3>
58
def get_metric_data(): url = "https://a.klaviyo.com/api/metrics" headers = { "accept": "application/json", "revision": "2023-02-22", "Authorization": f"Klaviyo-API-Key {os.getenv('KLAVIYO_PRIVATE_KEY')}" } response = requests.get(url, headers=headers) return response.json()["data"]<h3>Step 5</h3>
61
<p>Get the entire JSON for all metrics, filter through the JSON object in search of the metric name provided in the parameters, and return that metric’s id:</p>
59
<p>Get the entire JSON for all metrics, filter through the JSON object in search of the metric name provided in the parameters, and return that metric’s id:</p>
62
def process_metric_data_for_metric_id(retrieve_metric_data, metric_name): metric_id = "" for metric in retrieve_metric_data: if metric.get("attributes", {}).get("name", "") == metric_name: metric_id = metric["id"] break return metric_id<h3>Step 6</h3>
60
def process_metric_data_for_metric_id(retrieve_metric_data, metric_name): metric_id = "" for metric in retrieve_metric_data: if metric.get("attributes", {}).get("name", "") == metric_name: metric_id = metric["id"] break return metric_id<h3>Step 6</h3>
63
<p>Pass in metric id as a parameter and use it to find every event associated with the metric:</p>
61
<p>Pass in metric id as a parameter and use it to find every event associated with the metric:</p>
64
def get_metric_events(metric_id): if not metric_id: return [] baseurl = "https://a.klaviyo.com/api/events/?filter=equals(metric_id,\"" + metric_id + "\")&fields[profile]=first_name,email&include=profiles" headers = { "accept": "application/json", "revision": "2023-02-22", "Authorization": f"Klaviyo-API-Key {os.getenv('KLAVIYO_PRIVATE_KEY')}" } response = requests.get(baseurl, headers=headers) response_data = response.json().get("data",[]) next_url = response.json()["links"].get("next", "") counter = 1 while next_url: counter += 1 print(f"processing page {counter}") response = requests.get(next_url, headers=headers) response_data += response.json().get("data",[]) next_url = response.json()["links"].get("next", "") return response_data<h3>Step 7</h3>
62
def get_metric_events(metric_id): if not metric_id: return [] baseurl = "https://a.klaviyo.com/api/events/?filter=equals(metric_id,\"" + metric_id + "\")&fields[profile]=first_name,email&include=profiles" headers = { "accept": "application/json", "revision": "2023-02-22", "Authorization": f"Klaviyo-API-Key {os.getenv('KLAVIYO_PRIVATE_KEY')}" } response = requests.get(baseurl, headers=headers) response_data = response.json().get("data",[]) next_url = response.json()["links"].get("next", "") counter = 1 while next_url: counter += 1 print(f"processing page {counter}") response = requests.get(next_url, headers=headers) response_data += response.json().get("data",[]) next_url = response.json()["links"].get("next", "") return response_data<h3>Step 7</h3>
65
<p>Filter through the JSON data for the metric’s events to extract the profile id, subscribed timestamp, email consent, profile first name, and profile email:</p>
63
<p>Filter through the JSON data for the metric’s events to extract the profile id, subscribed timestamp, email consent, profile first name, and profile email:</p>
66
def filter_specific_metric_data_for_field_data(retrieve_metric_response): if not retrieve_metric_response: return [] filtered_metric_data = [] print(retrieve_metric_response) retrieve_metric_data = retrieve_metric_response["data"] retrieve_metric_included = retrieve_metric_response["included"] for retrieve_metric in retrieve_metric_data: data_profile_id = retrieve_metric.get("attributes", {}).get("profile_id", {}) for retrieve_profile in retrieve_metric_included: if data_profile_id == retrieve_profile.get("id"): filtered_metric_data.append({ "profile_id": retrieve_metric.get("attributes", {}).get("profile_id", {}), "subscription_updated_date": retrieve_metric.get("attributes", {}).get("datetime", {}), "profile_email_consent": "suppressed", "included_profile_firstname": retrieve_profile.get("attributes", {}).get("first_name", ""), "included_profile_email": retrieve_profile.get("attributes", {}).get("email", "") }) break return filtered_metric_data<h3>Step 8</h3>
64
def filter_specific_metric_data_for_field_data(retrieve_metric_response): if not retrieve_metric_response: return [] filtered_metric_data = [] print(retrieve_metric_response) retrieve_metric_data = retrieve_metric_response["data"] retrieve_metric_included = retrieve_metric_response["included"] for retrieve_metric in retrieve_metric_data: data_profile_id = retrieve_metric.get("attributes", {}).get("profile_id", {}) for retrieve_profile in retrieve_metric_included: if data_profile_id == retrieve_profile.get("id"): filtered_metric_data.append({ "profile_id": retrieve_metric.get("attributes", {}).get("profile_id", {}), "subscription_updated_date": retrieve_metric.get("attributes", {}).get("datetime", {}), "profile_email_consent": "suppressed", "included_profile_firstname": retrieve_profile.get("attributes", {}).get("first_name", ""), "included_profile_email": retrieve_profile.get("attributes", {}).get("email", "") }) break return filtered_metric_data<h3>Step 8</h3>
67
<p>Save the metric event data in another new .csv file:</p>
65
<p>Save the metric event data in another new .csv file:</p>
68
def save_filtered_data_as_csv(filtered_specific_metric_event_data, filename): with open(filename, 'w') as f: writer = csv.writer(f) writer.writerow(filtered_specific_metric_event_data[0].keys()) for filtered_specific_metric in filtered_specific_metric_event_data: writer.writerow(filtered_specific_metric.values())<h3>Step 9</h3>
66
def save_filtered_data_as_csv(filtered_specific_metric_event_data, filename): with open(filename, 'w') as f: writer = csv.writer(f) writer.writerow(filtered_specific_metric_event_data[0].keys()) for filtered_specific_metric in filtered_specific_metric_event_data: writer.writerow(filtered_specific_metric.values())<h3>Step 9</h3>
69
<p>Combine the two .csv files-metric events and profile data-into one:</p>
67
<p>Combine the two .csv files-metric events and profile data-into one:</p>
70
def merge_lists(list_of_lists): merged_lists = [] for data_list in list_of_lists: merged_lists += data_list return merged_lists<h3>Step 10</h3>
68
def merge_lists(list_of_lists): merged_lists = [] for data_list in list_of_lists: merged_lists += data_list return merged_lists<h3>Step 10</h3>
71
<p>When the main function is called, all the other functions are also called to run and generate the .csv files with the historical subscription data:</p>
69
<p>When the main function is called, all the other functions are also called to run and generate the .csv files with the historical subscription data:</p>
72
def main(): retrieved_metric_data = get_metric_data() # Step 1: Retrieve metric ids for all metrics that lead to consent or suppression retrieved_metric_unsubscribe_id = process_metric_data_for_metric_id(retrieved_metric_data, "Unsubscribe") retrieved_metric_subscribe_id = process_metric_data_for_metric_id(retrieved_metric_data, "Subscribe") retrieved_metric_unsubscribe_from_list_id = process_metric_data_for_metric_id(retrieved_metric_data,"Unsubscribe from List") retrieved_metric_marked_spam_id = process_metric_data_for_metric_id(retrieved_metric_data, "Marked Email as Spam") retrieved_metric_consented_sms_id = process_metric_data_for_metric_id(retrieved_metric_data,"Consented to Receive SMS") retrieved_metric_unsubscribed_from_sms_id = process_metric_data_for_metric_id(retrieved_metric_data,"Unsubscribed from SMS") # Step 2: Get event data for each metric retrieved_filter_unsubscribe_metric_data_for_field_data = get_metric_events(retrieved_metric_unsubscribe_id) retrieved_filter_subscribe_metric_data_for_field_data = get_metric_events(retrieved_metric_subscribe_id) retrieved_filter_unsubscribe_from_list_metric_data_for_field_data = get_metric_events(retrieved_metric_unsubscribe_from_list_id) retrieved_filter_marked_as_spam_metric_data_for_field_data = get_metric_events(retrieved_metric_marked_spam_id) retrieved_filter_consented_sms_metric_data_for_field_data = get_metric_events(retrieved_metric_consented_sms_id) retrieved_filter_unsubscribed_from_sms_metric_data_for_field_data = get_metric_events(retrieved_metric_unsubscribed_from_sms_id) # Step 3: Filter event data for consent properties filtered_metric_unsubscribe_event_data = filter_specific_metric_data_for_field_data( retrieved_filter_unsubscribe_metric_data_f or_field_data) filtered_metric_subscribe_event_data = filter_specific_metric_data_for_field_data( retrieved_filter_subscribe_metric_data_for_field_data) filtered_metric_unsubscribe_from_list_event_data = filter_specific_metric_data_for_field_data( retrieved_filter_unsubscribe_from_list_metric_data_for_field_data) filtered_metric_marked_as_spam_event_data = filter_specific_metric_data_for_field_data( retrieved_filter_marked_as_spam_metric_data_for_field_data) filtered_metric_consented_sms_event_data = filter_specific_metric_data_for_field_data( retrieved_filter_consented_sms_metric_data_for_field_data) filtered_metric_unsubscribed_from_sms_event_data = filter_specific_metric_data_for_field_data( retrieved_filter_unsubscribed_from_sms_metric_data_for_field_data) # Step 4: Retrieves all profile data retrieve_profile_data = get_profiles() filtered_profile_subscription_data = process_profile_data(retrieve_profile_data) # Step 5: Combines the lists of all the filtered data clean_merged_file = merge_lists([ filtered_profile_subscription_data, filtered_metric_unsubscribe_event_data, filtered_metric_subscribe_event_data, filtered_metric_unsubscribe_from_list_event_data, filtered_metric_marked_as_spam_event_data, filtered_metric_consented_sms_event_data, filtered_metric_unsubscribed_from_sms_event_data ]) # Step 6: Saves data as a csv save_filtered_data_as_csv(clean_merged_file, "clean_merged_data.csv") main()<h3>Step 11</h3>
70
def main(): retrieved_metric_data = get_metric_data() # Step 1: Retrieve metric ids for all metrics that lead to consent or suppression retrieved_metric_unsubscribe_id = process_metric_data_for_metric_id(retrieved_metric_data, "Unsubscribe") retrieved_metric_subscribe_id = process_metric_data_for_metric_id(retrieved_metric_data, "Subscribe") retrieved_metric_unsubscribe_from_list_id = process_metric_data_for_metric_id(retrieved_metric_data,"Unsubscribe from List") retrieved_metric_marked_spam_id = process_metric_data_for_metric_id(retrieved_metric_data, "Marked Email as Spam") retrieved_metric_consented_sms_id = process_metric_data_for_metric_id(retrieved_metric_data,"Consented to Receive SMS") retrieved_metric_unsubscribed_from_sms_id = process_metric_data_for_metric_id(retrieved_metric_data,"Unsubscribed from SMS") # Step 2: Get event data for each metric retrieved_filter_unsubscribe_metric_data_for_field_data = get_metric_events(retrieved_metric_unsubscribe_id) retrieved_filter_subscribe_metric_data_for_field_data = get_metric_events(retrieved_metric_subscribe_id) retrieved_filter_unsubscribe_from_list_metric_data_for_field_data = get_metric_events(retrieved_metric_unsubscribe_from_list_id) retrieved_filter_marked_as_spam_metric_data_for_field_data = get_metric_events(retrieved_metric_marked_spam_id) retrieved_filter_consented_sms_metric_data_for_field_data = get_metric_events(retrieved_metric_consented_sms_id) retrieved_filter_unsubscribed_from_sms_metric_data_for_field_data = get_metric_events(retrieved_metric_unsubscribed_from_sms_id) # Step 3: Filter event data for consent properties filtered_metric_unsubscribe_event_data = filter_specific_metric_data_for_field_data( retrieved_filter_unsubscribe_metric_data_f or_field_data) filtered_metric_subscribe_event_data = filter_specific_metric_data_for_field_data( retrieved_filter_subscribe_metric_data_for_field_data) filtered_metric_unsubscribe_from_list_event_data = filter_specific_metric_data_for_field_data( retrieved_filter_unsubscribe_from_list_metric_data_for_field_data) filtered_metric_marked_as_spam_event_data = filter_specific_metric_data_for_field_data( retrieved_filter_marked_as_spam_metric_data_for_field_data) filtered_metric_consented_sms_event_data = filter_specific_metric_data_for_field_data( retrieved_filter_consented_sms_metric_data_for_field_data) filtered_metric_unsubscribed_from_sms_event_data = filter_specific_metric_data_for_field_data( retrieved_filter_unsubscribed_from_sms_metric_data_for_field_data) # Step 4: Retrieves all profile data retrieve_profile_data = get_profiles() filtered_profile_subscription_data = process_profile_data(retrieve_profile_data) # Step 5: Combines the lists of all the filtered data clean_merged_file = merge_lists([ filtered_profile_subscription_data, filtered_metric_unsubscribe_event_data, filtered_metric_subscribe_event_data, filtered_metric_unsubscribe_from_list_event_data, filtered_metric_marked_as_spam_event_data, filtered_metric_consented_sms_event_data, filtered_metric_unsubscribed_from_sms_event_data ]) # Step 6: Saves data as a csv save_filtered_data_as_csv(clean_merged_file, "clean_merged_data.csv") main()<h3>Step 11</h3>
73
<p>Download the files from Github and run the script for an account requesting an audit trail of subscription events. The files are accessible through this<a>Github repository</a>.</p>
71
<p>Download the files from Github and run the script for an account requesting an audit trail of subscription events. The files are accessible through this<a>Github repository</a>.</p>
74
<p>Download the published files for the subscription-suppression log history from Klaviyo’s Github workspace. Open the files in Pycharm, Jupyter Notebook, or any other<a>Python IDE</a>.</p>
72
<p>Download the published files for the subscription-suppression log history from Klaviyo’s Github workspace. Open the files in Pycharm, Jupyter Notebook, or any other<a>Python IDE</a>.</p>
75
<p>Ensure you have the following libraries installed in Pycharm or whichever development environment you prefer: os, csv, requests, and load_dotenv.</p>
73
<p>Ensure you have the following libraries installed in Pycharm or whichever development environment you prefer: os, csv, requests, and load_dotenv.</p>
76
<p>All files in the next image should appear in the IDE when you open the file package. If any of them are missing, repeat the download process to make sure you didn’t miss a step:</p>
74
<p>All files in the next image should appear in the IDE when you open the file package. If any of them are missing, repeat the download process to make sure you didn’t miss a step:</p>
77
<p>In the .env file, replace the<strong>existing API key</strong>with the<strong>private API key of the account</strong>you’re investigating:</p>
75
<p>In the .env file, replace the<strong>existing API key</strong>with the<strong>private API key of the account</strong>you’re investigating:</p>
78
<p>Open the Subscription_log_history.py file and run it:</p>
76
<p>Open the Subscription_log_history.py file and run it:</p>
79
<p>This should update the clean_merged_data file with the preferred account’s data:</p>
77
<p>This should update the clean_merged_data file with the preferred account’s data:</p>
80
<p>Right click on the file name, clean_merged_data.csv, to open it in the Numbers application:</p>
78
<p>Right click on the file name, clean_merged_data.csv, to open it in the Numbers application:</p>
81
<h2>Impact</h2>
79
<h2>Impact</h2>
82
<p>By generating a human-readable .csv file, this Solution Recipe equips all Klaviyo employees and users with the assurance and evidence they need to discuss a profile’s subscription history. It also empowers the customer success organization to efficiently handle cases pertaining to subscriptions and suppressions, while cultivating trust among Klaviyo-valued users. Above all, this Solution Recipe enables Klaviyo to consistently uphold its commitment to customer satisfaction by proactively anticipating user requirements and offering readily accessible solutions for these needs.</p>
80
<p>By generating a human-readable .csv file, this Solution Recipe equips all Klaviyo employees and users with the assurance and evidence they need to discuss a profile’s subscription history. It also empowers the customer success organization to efficiently handle cases pertaining to subscriptions and suppressions, while cultivating trust among Klaviyo-valued users. Above all, this Solution Recipe enables Klaviyo to consistently uphold its commitment to customer satisfaction by proactively anticipating user requirements and offering readily accessible solutions for these needs.</p>
83
<h2>Learn more</h2>
81
<h2>Learn more</h2>
84
<h3>Error handling</h3>
82
<h3>Error handling</h3>
85
<p>If you encounter any errors in the code, we recommend using online resources, such as Stackoverflow, Notion, or the Klaviyo Support team.</p>
83
<p>If you encounter any errors in the code, we recommend using online resources, such as Stackoverflow, Notion, or the Klaviyo Support team.</p>
86
<h3>Rate limits</h3>
84
<h3>Rate limits</h3>
87
<p>Keep in mind that there are rate limits for each endpoint in the code. You can address these limits by practicing pauses in between code runs to ensure that there is enough time between each attempt. Alternatively, you can customize the code to retry automatically when a rate limit occurs.</p>
85
<p>Keep in mind that there are rate limits for each endpoint in the code. You can address these limits by practicing pauses in between code runs to ensure that there is enough time between each attempt. Alternatively, you can customize the code to retry automatically when a rate limit occurs.</p>
88
<p>For the following endpoints, the rate limits are enforced at the following speeds:</p>
86
<p>For the following endpoints, the rate limits are enforced at the following speeds:</p>
89
<ul><li>Profiles endpoint: burst of 75/s and steady of 700/m</li>
87
<ul><li>Profiles endpoint: burst of 75/s and steady of 700/m</li>
90
<li>Events endpoint: burst of 350/s and steady of 3500/m</li>
88
<li>Events endpoint: burst of 350/s and steady of 3500/m</li>
91
<li>Metrics endpoint: burst of 10/s and steady of 150/m</li>
89
<li>Metrics endpoint: burst of 10/s and steady of 150/m</li>
92
</ul><p>Want to learn more about Klaviyo’s developer experience and capabilities? Visit the<a>developer portal</a>.</p>
90
</ul><p>Want to learn more about Klaviyo’s developer experience and capabilities? Visit the<a>developer portal</a>.</p>
93
<h2>About the Solutions Architect Mentorship Program</h2>
91
<h2>About the Solutions Architect Mentorship Program</h2>
94
<p>At Klaviyo, our solutions architects are a core aspect of the customer experience, both before and after the point of sale. But despite the solutions architect team’s consistent influence throughout the customer lifecycle, it can be difficult to fully articulate what they do and how they do it.</p>
92
<p>At Klaviyo, our solutions architects are a core aspect of the customer experience, both before and after the point of sale. But despite the solutions architect team’s consistent influence throughout the customer lifecycle, it can be difficult to fully articulate what they do and how they do it.</p>
95
<p>In order to share our learnings and evangelize the multifaceted nature of the role internally, we created the Solutions Architect Mentor Program. Anyone in the company is eligible to receive a mentorship from a Klaviyo solutions architect after a baseline technical evaluation.</p>
93
<p>In order to share our learnings and evangelize the multifaceted nature of the role internally, we created the Solutions Architect Mentor Program. Anyone in the company is eligible to receive a mentorship from a Klaviyo solutions architect after a baseline technical evaluation.</p>
96
<p>Within the mentorship program, mentees can build a prototype solution with the guidance of their mentors and present it to their fellow mentees. We’re excited to showcase these solutions as part of the Klaviyo developer blog and celebrate our mentees’ resourcefulness and creativity.</p>
94
<p>Within the mentorship program, mentees can build a prototype solution with the guidance of their mentors and present it to their fellow mentees. We’re excited to showcase these solutions as part of the Klaviyo developer blog and celebrate our mentees’ resourcefulness and creativity.</p>
97
95