HomeGuidesAPI Reference
ChangelogHelp CenterCommunityContact Us
Guides
These docs are for v2023-10-15. Click to read the latest docs for v2024-10-15.

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 endpointNew 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 under data.attributes): 

    Old structureNew structure
    customer_properties.$emailprofile.data.attributes.email
    customer_properties.$phone_numberprofile.data.attributes.phone_number
    customer_properties.$anonymousprofile.data.attributes.anonymous_id
    customer_properties.$idprofile.data.attributes.external_id
    customer_properties.$exchange_idprofile.data.attributes._kx
    customer_properties.$kidprofile.data.id
  • V1/v2 has the header content-type: application/x-www-form-urlencoded, whereas the new has content-type: application/json.

Digram showing relationship between Track endpoint and Create Event endpoint, mapping their properties

Migrate identify calls

Old endpointNew 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 structureNew structure
    properties.$emaildata.attributes.email
    properties.$phone_numberdata.attributes.phone_number
    properties.$anonymousdata.attributes.anonymous_id
    properties.$iddata.attributes.external_id
    properties.$exchange_iddata.attributes._kx
    properties.$kiddata.id
  • Certain location-related fields (e.g. $city, $region, $country, etc.) have been moved under data.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.

Comparison diagram between the structure of Identify Profile and Create Profile

Migrate subscribe profile calls

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 to subscriptions.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 the relationships section of the payload.

Migrate unsubscribe profile calls

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 endpointNew 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