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.
v1/v2 legacy APIs retired on June 30, 2024.
To learn more about Klaviyo’s API lifecycle, check out our updated API versioning and deprecation policy.
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: Use the Create or Update Profile endpoint. 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"
}
}
{
"data": {
"type": "profile",
"id": "ID",
"attributes": {
"email": "EMAIL",
"phone_number": "PHONE_NUMBER",
"external_id": "EXTERNAL_ID",
"anonymous_id": "ANONYMOUS_ID",
"_kx": "_KX",
"first_name": "FIRST_NAME",
"last_name": "LAST_NAME",
"organization": "ORGANIZATION",
"title": "TITLE",
"image": "IMAGE_URL",
"location": {
"address1": "ADDRESS_1",
"address2": "ADDRESS_2",
"city": "CITY",
"country": "COUNTRY",
"region": "REGION",
"zip": "ZIP",
"timezone": "TIMEZONE",
"ip": "IP"
},
"properties": {
"newKey": "New Value"
}
},
"meta": {
"patch_properties": {
"append": {
"newKey": "New Value"
},
"unappend": {
"newKey": "New Value"
},
"unset": "skus"
}
}
}
}
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 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.
Note that, at this moment, you cannot add profile properties to your Subscribe Profiles API call. If you want to add profile properties to a profile while subscribing it to a list, you can:
- Use the Create Client Subscription endpoint to include profile properties in the payload (only can subscribe one profile per call).
- Make a call to Create or Update Profile (Spawn Bulk Profile Import Job if creating/updating multiple profiles) with the desired properties, followed by a call to Subscribe Profiles for the created/updated profiles.
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
Exclude profile from all email marketing
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.
Get excluded and unsubscribed profiles
Old endpoint | New endpoint |
---|---|
Get Global Exclusions and Unsubscribes | Get Profiles (see Profiles API overview for more info) |
GET v1/people/exclusions
GET /api/profiles/profiles?filter=greater-or-equal(subscriptions.email.marketing.suppression.timestamp,YYYY-MM-DD)
Changes from v1/v2 to new APIs include:
- Filter by list suppression and time of suppression in a single API call.
- Fetch profiles by suppression data including suppression reason, timestamp, and list ID with the filters below:
Filter description | Filter structure | Available filters | Example |
---|---|---|---|
Get excluded profiles by timestamp of suppression | subscriptions.email.marketing.suppression.timestamp |
| ?filter=greater-or-equal(subscriptions.email.marketing.suppression.timestamp,YYYY-MM-DD) |
Get excluded profiles by suppression reason | subscriptions.email.marketing.suppression.reason | equals | ?filter=equals(subscriptions.email.marketing.suppression.reason,"UNSUBSCRIBE") Suppression reasons: UNSUBSCRIBE , SPAM_REPORT , BOUNCE , INVALID_EMAIL , USER_INITIATED . |
Get excluded profiles by timestamp of list suppression | subscriptions.email.marketing.list_suppressions.timestamp |
| ?filter=less-or-equal(subscriptions.email.marketing.list_suppressions.timestamp,YYYY-MM-DD) |
Get excluded profiles by list suppression reason | subscriptions.email.marketing.list_suppressions.reason | equals | ?filter=equals(subscriptions.email.marketing.list_suppressions.reason,"UNSUBSCRIBE") Suppression reasons: UNSUBSCRIBE , SPAM_REPORT , BOUNCE , INVALID_EMAIL , USER_INITIATED . |
Get excluded profiles by list ID | subscriptions.email.marketing.list_suppressions.list_id | equals | ?filter=greater-or-equal(subscriptions.email.marketing.list_suppressions.list_id,"LIST_ID") |
Additional resources
Updated about 1 month ago