Migrate track, identify, and subscribe to our new APIs
Learn how to migrate your code from our v1/v2 APIs to our new APIs. We’ll explain what corresponding calls to use, and any major changes.
Migrate track calls
Old endpoint | New endpoint |
---|---|
Track Profile Activity (v1/v2) If you are using the endpoint Track Profile Activity (Legacy), it works the same as the v1/v2 track endpoint, with two notable differences: the HTTP method is GET, and the data is base-64-encoded in a data query param. | Create Event (v2023-10-15) This is a server-side endpoint, which uses a private API key. See the explanation of changes below for client-side information. |
{
"token": "PUBLIC_KEY",
"event": "Added to Cart",
"customer_properties": {
"$email": "EMAIL_ADDRESS",
"$phone_number": "PHONE_NUMBER",
"$first_name": "FIRST_NAME",
"$last_name": "LAST_NAME",
"$city": "CITY",
"$region": "STATE/REGION",
"$country": "COUNTRY",
"$zip": "ZIP_CODE",
"$image": "IMAGE_URL",
"CUSTOM_PROPERTY": "CUSTOM_VALUE"
},
"properties": {
"$event_id": "UNIQUE_EVENT_ID",
"$value": 9.99,
"ProductName": "Winnie the Pooh",
"ProductID": "1111",
"Categories": [
"Fiction",
"Children"
],
"ImageURL": "http://www.example.com/path/to/product/image.png",
"URL": "http://www.example.com/path/to/product",
"Brand": "Kids Books",
"Price": 9.99,
"CompareAtPrice": 14.99
}
}
{
"data": {
"type": "event",
"attributes": {
"profile": {
"data": {
"type": "profile",
"attributes": {
"email": "EMAIL_ADDRESS",
"phone_number": "PHONE_NUMBER",
"first_name": "FIRST_NAME",
"last_name": "LAST_NAME",
"location": {
"city": "CITY",
"region": "STATE/REGION",
"country": "COUNTRY",
"zip": "ZIP_CODE"
},
"image": "IMAGE_URL",
"properties": {
"CUSTOM_PROPERTY": "CUSTOM_VALUE",
}
}
}
},
"metric": {
"data": {
"type": "metric",
"attributes": {
"name": "Added to Cart"
}
}
},
"properties": {
"ProductName": "Winnie the Pooh",
"ProductID": "1111",
"Categories": [
"Fiction",
"Children"
],
"ImageURL": "http://www.example.com/path/to/product/image.png",
"URL": "http://www.example.com/path/to/product",
"Brand": "Kids Books",
"Price": 9.99,
"CompareAtPrice": 14.99
},
"value": 99.99,
"time": "2023-07-01T00:00:00",
"unique_id": "UNIQUE_EVENT_ID"
}
}
Changes from v1/v2 to new APIs include:
-
There are now two endpoints, one for server-side contexts and one for client-side contexts.
-
The server-side endpoint leverages a private API key for auth, which must be passed in via the
Authorization
request header. -
If you are using the Client endpoint, it requires the public API key to be passed via the
company_id
query parameter and is intended to be called from client-side contexts, e.g. browsers and mobile apps. While these client endpoints are available, we typically recommend using Klaviyo.js and our mobile SDKs for this purpose instead. -
customer_properties
are now passed under profile with new identifier mapping (all identifier fields for the new endpoint are nested underdata.attributes
):Old structure New structure customer_properties.$email
profile.data.attributes.email
customer_properties.$phone_number
profile.data.attributes.phone_number
customer_properties.$anonymous
profile.data.attributes.anonymous_id
customer_properties.$id
profile.data.attributes.external_id
customer_properties.$exchange_id
profile.data.attributes._kx
customer_properties.$kid
profile.data.id
-
V1/v2 has the header content-type:
application/x-www-form-urlencoded
, whereas the new hascontent-type: application/json
.
Migrate identify calls
Old endpoint | New endpoint |
---|---|
Identify Profile (v1/v2) If you are using Identify Profile (Legacy) endpoint, it works the same as the v1/v2 identify endpoint, with two notable differences: the HTTP method is GET, and the data is base-64-encoded in a data query param. | Server-side, with a private API key: 1. First, check whether the profile exists using Get Profiles (v2023-10-15). Filter by your identifier like so: GET /api/profiles?filter=equals(email,”EMAIL_ADDRESS”) .2. If the profile exists, update it using the ID found in step 1, or create a new profile. a. If a profile exists, update it using Update Profile (v2023-10-15). b. If the profile does not exist, create one using Create Profile (v2023-10-15). If you want to use this functionality client-side, with a public API key, we recommend using Klaviyo.js to accomplish this. |
{
"token": "PUBLIC_API_KEY",
"properties": {
"$email": "EMAIL_ADDRESS",
"$phone_number": "PHONE_NUMBER",
"$first_name": "FIRST_NAME",
"$last_name": "LAST_NAME",
"$image": "IMAGE_URL",
"$city": "CITY",
"$region": "STATE/REGION",
"$country": "COUNTRY",
"$zip": "ZIP_CODE",
"CUSTOM_PROPERTY": "CUSTOM_VALUE"
}
}
//UPDATE PROFILE
{
"data": {
"type": "profile",
"id": "KLAVIYO_PROFILE_ID",
"attributes": {
"email": "EMAIL_ADDRESS",
"phone_number": "PHONE_NUMBER",
"first_name": "FIRST_NAME",
"last_name": "LAST_NAME",
"image": "IMAGE_URL",
"location": {
"city": "CITY",
"region": "STATE/REGION",
"country": "COUNTRY",
"zip": "ZIP_CODE"
},
"properties": {
"CUSTOM_PROPERTY": "CUSTOM_VALUE"
}
}
}
}
//CREATE PROFILE
{
"data": {
"type": "profile",
"attributes": {
"email": "EMAIL_ADDRESS",
"phone_number": "PHONE_NUMBER",
"first_name": "FIRST_NAME",
"last_name": "LAST_NAME",
"image": "IMAGE_URL",
"location": {
"city": "CITY",
"region": "STATE/REGION",
"country": "COUNTRY",
"zip": "ZIP_CODE"
},
"properties": {
"CUSTOM_PROPERTY": "CUSTOM_VALUE"
}
}
}
}
}
Changes from v1/v2 to new APIs include:
-
Identifier mapping:
Old structure New structure properties.$email
data.attributes.email
properties.$phone_number
data.attributes.phone_number
properties.$anonymous
data.attributes.anonymous_id
properties.$id
data.attributes.external_id
properties.$exchange_id
data.attributes._kx
properties.$kid
data.id
-
Certain location-related fields (e.g.
$city
,$region
,$country
, etc.) have been moved underdata.attributes.location
. -
Any other custom profile properties should be passed under
data.attributes.properties
. -
We’ve mostly moved away from $-prefixed properties. Any ones still remaining must be passed through via
data.attributes.properties
.
Migrate subscribe profile calls
Old endpoint | New endpoint |
---|---|
Subscribe Profiles to List (v1/v2) | Subscribe Profiles (v2023-10-15) |
POST /api/v2/list/{LIST_ID}/subscribe
{
"profiles": [
{
"email": "EMAIL_ADDRESS"
},
{
"phone_number": "PHONE_NUMBER",
"sms_consent": true
}
]
}
{
"data": {
"type": "profile-subscription-bulk-create-job",
"attributes": {
"profiles": {
"data": [
{
"type": "profile",
"attributes": {
"email": "EMAIL_ADDRESS",
"subscriptions": {
"email": {
"marketing": {
"consent": "SUBSCRIBED"
}
}
}
}
},
{
"type": "profile",
"attributes": {
"phone_number": "PHONE_NUMBER",
"subscriptions": {
"sms": {
"marketing": {
"consent": "SUBSCRIBED"
}
}
}
}
}
]
}
},
"relationships": {
"list": {
"data": {
"type": "list",
"id": "LIST_ID"
}
}
}
}
}
Changes from v1/v2 to new APIs include:
sms_consent
moved tosubscriptions.sms.marketing.consent
.- You must specify email consent explicitly under
subscriptions.email.marketing.consent
. - May need to chunk v1/v2 requests in order to not exceed 100 profiles per request limit on new requests.
LIST_ID
moved to therelationships
section of the payload.
Migrate unsubscribe profile calls
Old endpoint | New endpoint |
---|---|
Unsubscribe Profiles From List (v1/v2) | Unsubscribe Profiles (v2023-10-15) |
DELETE /api/v2/list/LIST_ID/unsubscribe
{
"emails": [
"EMAIL_ADDRESS_1",
"EMAIL_ADDRESS_2"
],
"phone_numbers": [
"PHONE_NUMBER"
]
}
{
"data": {
"type": "profile-subscription-bulk-delete-job",
"attributes": {
"profiles": {
"data": [
{
"type": "profile",
"attributes": {
"email": "EMAIL_ADDRESS_1",
}
},
{
"type": "profile",
"attributes": {
"email": "EMAIL_ADDRESS_2",
}
},
{
"type": "profile",
"attributes": {
"phone_number": "PHONE_NUMBER",
}
},
]
}
},
"relationships": {
"list": {
"data": {
"type": "list",
"id": "LIST_ID"
}
}
}
}
}
Changes from v1/v2 to new APIs include:
LIST_ID
moved from a path parameter in the URL to the relationships section of the payload. It is also optional on the new endpoint.
Migrate profile exclusion calls
Old endpoint | New endpoint |
---|---|
Exclude Profile From All Email Payload sent as x-www-form-urlencoded . | Suppress Profiles (v2023-10-15) |
POST v1/people/exclusions
'email=email-address'
POST /api/profile-suppression-bulk-create-jobs/
{
"data": {
"type": "profile-suppression-bulk-create-job",
"attributes": {
"profiles": {
"data": [
{
"type": "profile",
"attributes": {
"email": "EMAIL-ADDRESS"
}
}
]
}
}
}
}
Changes from v1/v2 to new APIs include:
- The new endpoint operates asynchronously, whereas the v1/v2 endpoint operates synchronously.
- In the v1/v2 endpoint, you had to exclude profiles from email one at a time. In the new endpoint, you can manually suppress up to 100 profiles from receiving marketing emails in a single API call.
- The request for the v1/v2 endpoint needed to be sent as a form encoded payload. The new endpoint accepts JSON only.
Additional resources
Updated about 1 year ago