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
Authorizationrequest header. -
If you are using the Client endpoint, it requires the public API key to be passed via the
company_idquery 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_propertiesare 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.$emailprofile.data.attributes.emailcustomer_properties.$phone_numberprofile.data.attributes.phone_numbercustomer_properties.$anonymousprofile.data.attributes.anonymous_idcustomer_properties.$idprofile.data.attributes.external_idcustomer_properties.$exchange_idprofile.data.attributes._kxcustomer_properties.$kidprofile.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.$emaildata.attributes.emailproperties.$phone_numberdata.attributes.phone_numberproperties.$anonymousdata.attributes.anonymous_idproperties.$iddata.attributes.external_idproperties.$exchange_iddata.attributes._kxproperties.$kiddata.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_consentmoved 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_IDmoved to therelationshipssection 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_IDmoved 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 year ago