Was this page helpful?

Experience API

Important: This Experience API is for Contentful Personalization use. If you are looking for the documentation for Studio, see the Studio section.

Table of contents

Overview

The Experience API is the core of Contentful Personalization. It returns profiles, JSON representations of visitors and their activity, in response to events. The Experience API is highly performant because of its deployment on the edge.

Response envelope

All Experience API responses use a standard envelope:

Success (200):

{
  "data": { ... },
  "error": null,
  "message": "ok"
}

Error:

{
  "message": "<description>",
  "data": {},
  "error": {
    "code": "ERR_..."
  }
}

The data field contains the response payload on success. On error, data is an empty object and error contains a machine-readable error code.

Error responses

All endpoints can return the following errors:

HTTP error.code Cause
400 ERR_INVALID_DATA Request body or query parameters fail validation.
404 ERR_CONFIG_NOT_FOUND Organization or environment not found.
404 ERR_PROFILE_NOT_FOUND Profile ID does not exist.
404 ERR_NAMESPACE_MISMATCH Profile belongs to a different organization/environment.
429 ERR_PROFILE_OVERLOAD Too many concurrent requests to this profile's state. To resolve this issue, send events less frequently, e.g. by implementing a backoff.
500 ERR_INTERNAL_SERVER_ERROR Unhandled server error.

Endpoints

Create profile

[POST]https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles

Path parameters

Parameter Type Description
environmentSlug string The slug of the environment of the profile to retrieve.
organizationId string The ID of the organization of the profile to retrieve.
Important: The organization ID (or API key) is the >Client ID field value displayed under the "SDK Keys" section, on the Optimization tab.

Client ID

Query parameters

Parameter Type Default Description
locale string en An ISO 639-1 language code used for experience evaluation.
type string Set to preflight to evaluate experiences without persisting state. Useful for SSR/ESR — the server gets variant selections for rendering, then the client SDK sends the real events on hydration.

Request

const response = await fetch('https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles', {
    method: 'POST',
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      "events": [
        {
          "channel": "web",
          "context": {
            "app": {
              "name": "Ninetailed Analytics SDK",
              "version": "1.0.0"
            },
            "library": {
              "name": "Ninetailed Analytics SDK",
              "version": "1.0.0"
            },
            "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
            "campaign": {},
            "locale": "en-US",
            "page": {
              "path": "/",
              "query": {
                "utm_campaign": "example_campaign",
                "utm_content": "example_content"
              },
              "referrer": "",
              "search": "?utm_campaign=example_campaign&utm_content=example_content",
              "url": "https://example.com/?utm_campaign=example_campaign&utm_content=example_content"
            }
          },
          "messageId": "abcd1234-efgh-5678-90ab-cdef11122222",
          "type": "page",
          "properties": {
            "title": "Sample Page",
            "url": "https://example.com/?utm_campaign=example_campaign&utm_content=example_content",
            "path": "/",
            "hash": "",
            "search": "?utm_campaign=example_campaign&utm_content=example_content",
            "width": "1920",
            "height": "1080",
            "query": {
              "utm_campaign": "example_campaign",
              "utm_content": "example_content"
            },
            "referrer": ""
          }
        }
      ]
    }),
});
const data = await response.json();
curl -L \
  -X POST \
  -H 'Content-Type: application/json' \
  'https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles' \
  -d '{"events":[{"channel":"web","context":{"app":{"name":"Ninetailed Analytics SDK","version":"1.0.0"},"library":{"name":"Ninetailed Analytics SDK","version":"1.0.0"},"userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36","campaign":{},"locale":"en-US","page":{"path":"/","query":{"utm_campaign":"example_campaign","utm_content":"example_content"},"referrer":"","search":"?utm_campaign=example_campaign&utm_content=example_content","url":"https://example.com/?utm_campaign=example_campaign&utm_content=example_content"}},"messageId":"abcd1234-efgh-5678-90ab-cdef11122222","type":"page","properties":{"title":"Sample Page","url":"https://example.com/?utm_campaign=example_campaign&utm_content=example_content","path":"/","hash":"","search":"?utm_campaign=example_campaign&utm_content=example_content","width":"1920","height":"1080","query":{"utm_campaign":"example_campaign","utm_content":"example_content"},"referrer":""}}]}'
import requests

response = requests.post(
    "https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles",
    headers={"Content-Type":"application/json"},
    json={"events":[{"channel":"web","context":{"app":{"name":"Ninetailed Analytics SDK","version":"1.0.0"},"library":{"name":"Ninetailed Analytics SDK","version":"1.0.0"},"userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36","campaign":{},"locale":"en-US","page":{"path":"/","query":{"utm_campaign":"example_campaign","utm_content":"example_content"},"referrer":"","search":"?utm_campaign=example_campaign&utm_content=example_content","url":"https://example.com/?utm_campaign=example_campaign&utm_content=example_content"}},"messageId":"abcd1234-efgh-5678-90ab-cdef11122222","type":"page","properties":{"title":"Sample Page","url":"https://example.com/?utm_campaign=example_campaign&utm_content=example_content","path":"/","hash":"","search":"?utm_campaign=example_campaign&utm_content=example_content","width":"1920","height":"1080","query":{"utm_campaign":"example_campaign","utm_content":"example_content"},"referrer":""}}]}
)
data = response.json()
POST /v2/organizations/{organizationId}/environments/{environmentSlug}/profiles HTTP/1.1
Host: experience.ninetailed.co
Content-Type: application/json
Accept: */*

{
  "events": [
    {
      "channel": "web",
      "context": {
        "app": {
          "name": "Ninetailed Analytics SDK",
          "version": "1.0.0"
        },
        "library": {
          "name": "Ninetailed Analytics SDK",
          "version": "1.0.0"
        },
        "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
        "campaign": {},
        "locale": "en-US",
        "page": {
          "path": "/",
          "query": {
            "utm_campaign": "example_campaign",
            "utm_content": "example_content"
          },
          "referrer": "",
          "search": "?utm_campaign=example_campaign&utm_content=example_content",
          "url": "https://example.com/?utm_campaign=example_campaign&utm_content=example_content"
        }
      },
      "messageId": "abcd1234-efgh-5678-90ab-cdef11122222",
      "type": "page",
      "properties": {
        "title": "Sample Page",
        "url": "https://example.com/?utm_campaign=example_campaign&utm_content=example_content",
        "path": "/",
        "hash": "",
        "search": "?utm_campaign=example_campaign&utm_content=example_content",
        "width": "1920",
        "height": "1080",
        "query": {
          "utm_campaign": "example_campaign",
          "utm_content": "example_content"
        },
        "referrer": ""
      }
    }
  ]
}

Response

{
  "data": {
    "profile": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "stableId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "random": 0.42,
      "audiences": [],
      "traits": {},
      "location": {
        "city": "Berlin",
        "region": "Berlin",
        "country": "Germany",
        "countryCode": "DE",
        "continent": "EU",
        "timezone": "Europe/Berlin"
      },
      "session": {
        "id": "sess-1234",
        "isReturningVisitor": false,
        "landingPage": {
          "path": "/",
          "url": "https://example.com/?utm_campaign=example_campaign&utm_content=example_content",
          "query": { "utm_campaign": "example_campaign", "utm_content": "example_content" },
          "referrer": "",
          "search": "?utm_campaign=example_campaign&utm_content=example_content"
        },
        "count": 1,
        "activeSessionLength": 0,
        "averageSessionLength": 0
      }
    },
    "experiences": [
      {
        "experienceId": "exp-1",
        "variantIndex": 0,
        "variants": { "entryA": "entryA" }
      }
    ],
    "changes": []
  },
  "error": null,
  "message": "ok"
}

Update profile

[POST]https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId}

Path parameters

Parameter Type Description
environmentSlug string The slug of the environment of the profile to retrieve.
organizationId string The ID of the organization of the profile to retrieve.
profileId string The ID of the profile to retrieve.
Important: The organization ID (or API key) is the Client ID field value displayed under the "SDK Keys" section, on the Optimization tab.

Client ID

Query parameters

Parameter Type Default Description
locale string en An ISO 639-1 language code used for experience evaluation.
type string Set to preflight to evaluate experiences without persisting state. Useful for SSR/ESR — the server gets variant selections for rendering, then the client SDK sends the real events on hydration.

Request

const response = await fetch('https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId}', {
    method: 'POST',
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      "events": [
        {
          "channel": "web",
          "context": {
            "app": {
              "name": "Ninetailed Analytics SDK",
              "version": "1.0.0"
            },
            "library": {
              "name": "Ninetailed Analytics SDK",
              "version": "1.0.0"
            },
            "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
            "campaign": {},
            "locale": "en-US",
            "page": {
              "path": "/",
              "query": {},
              "referrer": "",
              "search": "",
              "url": "https://example.com/"
            }
          },
          "messageId": "58f358f2-a858-11ed-afa1-0242ac120002",
          "timestamp": "2022-10-10T14:36:51.262Z",
          "type": "identify",
          "traits": {
            "favAnimal": "fox"
          },
          "userId": "asdf"
        }
      ]
    }),
});
const data = await response.json();
curl -L \
  -X POST \
  -H 'Content-Type: application/json' \
  'https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId}' \
  -d '{"events":[{"channel":"web","context":{"app":{"name":"Ninetailed Analytics SDK","version":"1.0.0"},"library":{"name":"Ninetailed Analytics SDK","version":"1.0.0"},"userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36","campaign":{},"locale":"en-US","page":{"path":"/","query":{},"referrer":"","search":"","url":"https://example.com/"}},"messageId":"58f358f2-a858-11ed-afa1-0242ac120002","timestamp":"2022-10-10T14:36:51.262Z","type":"identify","traits":{"favAnimal":"fox"},"userId":"asdf"}]}'
import requests

response = requests.post(
    "https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId}",
    headers={"Content-Type":"application/json"},
    json={"events":[{"channel":"web","context":{"app":{"name":"Ninetailed Analytics SDK","version":"1.0.0"},"library":{"name":"Ninetailed Analytics SDK","version":"1.0.0"},"userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36","campaign":{},"locale":"en-US","page":{"path":"/","query":{},"referrer":"","search":"","url":"https://example.com/"}},"messageId":"58f358f2-a858-11ed-afa1-0242ac120002","timestamp":"2022-10-10T14:36:51.262Z","type":"identify","traits":{"favAnimal":"fox"},"userId":"asdf"}]}
)
data = response.json()
POST /v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId} HTTP/1.1
Host: experience.ninetailed.co
Content-Type: application/json
Accept: */*

{
  "events": [
    {
      "channel": "web",
      "context": {
        "app": {
          "name": "Ninetailed Analytics SDK",
          "version": "1.0.0"
        },
        "library": {
          "name": "Ninetailed Analytics SDK",
          "version": "1.0.0"
        },
        "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
        "campaign": {},
        "locale": "en-US",
        "page": {
          "path": "/",
          "query": {},
          "referrer": "",
          "search": "",
          "url": "https://example.com/"
        }
      },
      "messageId": "58f358f2-a858-11ed-afa1-0242ac120002",
      "timestamp": "2022-10-10T14:36:51.262Z",
      "type": "identify",
      "traits": {
        "favAnimal": "fox"
      },
      "userId": "asdf"
    }
  ]
}

Response

{
  "data": {
    "profile": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "stableId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "random": 0.42,
      "audiences": ["audience-1"],
      "traits": {
        "favAnimal": "fox"
      },
      "location": { ... },
      "session": { ... }
    },
    "experiences": [
      {
        "experienceId": "exp-1",
        "variantIndex": 1,
        "variants": { "entryA": "entryB" }
      }
    ],
    "changes": [
      {
        "key": "heroTitle",
        "type": "Variable",
        "value": "Welcome back!",
        "meta": {
          "experienceId": "exp-2",
          "variantIndex": 1
        }
      }
    ]
  },
  "error": null,
  "message": "ok"
}

Get profile

[GET]https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId}

Path parameters

Parameter Type Description
environmentSlug string The slug of the environment of the profile to retrieve.
organizationId string The ID of the organization of the profile to retrieve.
profileId string The ID of the profile to retrieve.
Important: The organization ID (or API key) is the Client ID field value displayed under the "SDK Keys" section, on the Optimization tab.

Client ID

Query parameters

Parameter Type Default Description
locale string en An ISO 639-1 language code used for experience evaluation.

Request

const response = await fetch('https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId}', {
    method: 'GET',
    headers: {},
});
const data = await response.json();
curl -L \
  'https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId}'
import requests

response = requests.get(
    "https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId}",
    headers={},
)
data = response.json()
GET /v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId} HTTP/1.1
Host: experience.ninetailed.co
Accept: */*

Response

{
  "data": {
    "profile": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "stableId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "random": 0.42,
      "audiences": ["audience-1"],
      "traits": { "favAnimal": "fox" },
      "location": { ... },
      "session": { ... }
    },
    "experiences": [ ... ],
    "changes": [ ... ]
  },
  "error": null,
  "message": "ok"
}

Batch upsert profiles (sync)

[POST]https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/events

You can use this endpoint to push data from your own existing customer data systems into Contentful Personalization profiles in real-time, in periodic batches, or in bulk, as part of a first-time migration.

This endpoint upserts profiles, meaning that when events are passed for profile IDs that do not yet exist, profiles at those IDs will be created. This ensures that you do not have to check whether profiles at specific IDs exist prior to migration.

NOTE: This endpoint is intended to be used server-side. Because of that, it does not accept options related to resolving location or IP address. You may, however, still specify location data on events.

This endpoint accepts up to 200 events, with a maximum of 50 identify events, across a maximum of 50 unique profiles.

NOTE: Unlike the single-profile endpoints (Create, Update, Get), the batch endpoint returns profiles only — it does not return experiences or changes. If you need variant selections, use the single-profile endpoints instead.

Path parameters

Parameter Type Description
environmentSlug string The slug of the environment of the profile to retrieve.
organizationId string The ID of the organization of the profile to retrieve.

Please replace this note with the one above. This was the previous note, when the legacy Ninetailed app was live.

Important: The organization ID (or API key) is the Client ID field value displayed under the "SDK Keys" section, on the Optimization tab.

Client ID

Query parameters

Parameter Type Default Description
locale string en An ISO 639-1 language code used for experience evaluation.

Request

const response = await fetch('https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/events', {
    method: 'POST',
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      "events": [
        {
          "channel": "web",
          "context": {
            "library": {
              "name": "Ninetailed Analytics SDK",
              "version": "1.0.0"
            },
            "locale": "en-US",
            "page": {
              "path": "/",
              "query": {},
              "referrer": "",
              "search": "",
              "url": "https://example.com/"
            }
          },
          "messageId": "58f358f2-a858-11ed-afa1-0242ac120002",
          "anonymousId": "18635fe9cc665fd113547e897c13862b",
          "timestamp": "2022-01-01T00:00:00.000Z",
          "type": "page",
          "properties": {
            "title": "Sample Page",
            "url": "https://example.com/",
            "path": "/",
            "hash": "",
            "search": "",
            "width": 1419,
            "height": 1226,
            "query": {},
            "referrer": ""
          }
        },
        {
          "channel": "web",
          "context": {
            "library": {
              "name": "Ninetailed Analytics SDK",
              "version": "1.0.0"
            },
            "locale": "en-US",
            "page": {
              "path": "/",
              "query": {},
              "referrer": "",
              "search": "",
              "url": "https://example.com/"
            }
          },
          "messageId": "68a459e3-b959-22fe-bfb2-1353bd230003",
          "anonymousId": "18635fe9cc665fd113547e897c13862b",
          "userId": "user1",
          "traits": {},
          "timestamp": "2022-01-01T00:00:01.000Z",
          "type": "identify"
        }
      ]
    }),
});
const data = await response.json();
curl -L \
  -X POST \
  -H 'Content-Type: application/json' \
  'https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/events' \
  -d '{"events":[{"channel":"web","context":{"library":{"name":"Ninetailed Analytics SDK","version":"1.0.0"},"locale":"en-US","page":{"path":"/","query":{},"referrer":"","search":"","url":"https://example.com/"}},"messageId":"58f358f2-a858-11ed-afa1-0242ac120002","anonymousId":"18635fe9cc665fd113547e897c13862b","timestamp":"2022-01-01T00:00:00.000Z","type":"page","properties":{"title":"Sample Page","url":"https://example.com/","path":"/","hash":"","search":"","width":1419,"height":1226,"query":{},"referrer":""}},{"channel":"web","context":{"library":{"name":"Ninetailed Analytics SDK","version":"1.0.0"},"locale":"en-US","page":{"path":"/","query":{},"referrer":"","search":"","url":"https://example.com/"}},"messageId":"68a459e3-b959-22fe-bfb2-1353bd230003","anonymousId":"18635fe9cc665fd113547e897c13862b","userId":"user1","traits":{},"timestamp":"2022-01-01T00:00:01.000Z","type":"identify"}]}'
import requests

response = requests.post(
    "https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/events",
    headers={"Content-Type":"application/json"},
    json={"events":[{"channel":"web","context":{"library":{"name":"Ninetailed Analytics SDK","version":"1.0.0"},"locale":"en-US","page":{"path":"/","query":{},"referrer":"","search":"","url":"https://example.com/"}},"messageId":"58f358f2-a858-11ed-afa1-0242ac120002","anonymousId":"18635fe9cc665fd113547e897c13862b","timestamp":"2022-01-01T00:00:00.000Z","type":"page","properties":{"title":"Sample Page","url":"https://example.com/","path":"/","hash":"","search":"","width":1419,"height":1226,"query":{},"referrer":""}},{"channel":"web","context":{"library":{"name":"Ninetailed Analytics SDK","version":"1.0.0"},"locale":"en-US","page":{"path":"/","query":{},"referrer":"","search":"","url":"https://example.com/"}},"messageId":"68a459e3-b959-22fe-bfb2-1353bd230003","anonymousId":"18635fe9cc665fd113547e897c13862b","userId":"user1","traits":{},"timestamp":"2022-01-01T00:00:01.000Z","type":"identify"}]}
)
data = response.json()
POST /v2/organizations/{organizationId}/environments/{environmentSlug}/events HTTP/1.1
Host: experience.ninetailed.co
Content-Type: application/json
Accept: */*

{
  "events": [
    {
      "channel": "web",
      "context": {
        "library": {
          "name": "Ninetailed Analytics SDK",
          "version": "1.0.0"
        },
        "locale": "en-US",
        "page": {
          "path": "/",
          "query": {},
          "referrer": "",
          "search": "",
          "url": "https://example.com/"
        }
      },
      "messageId": "58f358f2-a858-11ed-afa1-0242ac120002",
      "anonymousId": "18635fe9cc665fd113547e897c13862b",
      "timestamp": "2022-01-01T00:00:00.000Z",
      "type": "page",
      "properties": {
        "title": "Sample Page",
        "url": "https://example.com/",
        "path": "/",
        "hash": "",
        "search": "",
        "width": 1419,
        "height": 1226,
        "query": {},
        "referrer": ""
      }
    },
    {
      "channel": "web",
      "context": {
        "library": {
          "name": "Ninetailed Analytics SDK",
          "version": "1.0.0"
        },
        "locale": "en-US",
        "page": {
          "path": "/",
          "query": {},
          "referrer": "",
          "search": "",
          "url": "https://example.com/"
        }
      },
      "messageId": "68a459e3-b959-22fe-bfb2-1353bd230003",
      "anonymousId": "18635fe9cc665fd113547e897c13862b",
      "userId": "user1",
      "traits": {},
      "timestamp": "2022-01-01T00:00:01.000Z",
      "type": "identify"
    }
  ]
}

Response

Returns one profile per unique anonymousId in the batch. Does not include experiences or changes.

{
  "data": {
    "profiles": [
      {
        "id": "18635fe9cc665fd113547e897c13862b",
        "stableId": "18635fe9cc665fd113547e897c13862b",
        "random": 0.73,
        "audiences": [],
        "traits": {},
        "location": {},
        "session": { ... }
      }
    ]
  },
  "error": null,
  "message": "ok"
}

Batch upsert profiles (async)

[POST]https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/import/events

This endpoint accepts the same request body as Batch upsert profiles (sync) but processes events asynchronously. It returns immediately with a confirmation message and processes events in the background.

This is a fire-and-forget endpoint suited for server-side integrations that do not need to render personalized content in the response.

NOTE: Because processing is deferred, this endpoint returns 200 even if the organization or environment does not exist. Errors are handled internally during background processing.

Path parameters

Parameter Type Description
environmentSlug string The slug of the environment of the profile to retrieve.
organizationId string The ID of the organization of the profile to retrieve.
Important: The organization ID (or API key) is the Client ID field value displayed under the "SDK Keys" section, on the Optimization tab.

Client ID

Query parameters

Parameter Type Default Description
locale string en An ISO 639-1 language code used for experience evaluation.

Request

The request body is the same as Batch upsert profiles (sync). See that section for examples.

Response

{
  "data": {
    "message": "Events accepted."
  },
  "error": null,
  "message": "ok"
}

Delete profile

[DELETE]https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId}

Path parameters

Parameter Type Description
environmentSlug string The slug of the environment of the profile to delete.
organizationId string The ID of the organization of the profile to delete.
profileId string The ID of the profile to delete.
Important: The organization ID (or API key) is the >Client ID field value displayed under the "SDK Keys" section, on the Optimization tab.

Client ID

Request

const response = await fetch('https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId}', {
    method: 'DELETE',
});
const data = await response.json();
curl -L \
  -X DELETE \
  'https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId}'
import requests

response = requests.delete(
    "https://experience.ninetailed.co/v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId}",
)
data = response.json()
DELETE /v2/organizations/{organizationId}/environments/{environmentSlug}/profiles/{profileId} HTTP/1.1
Host: experience.ninetailed.co
Accept: */*

Response

{
  "data": {},
  "error": null,
  "message": "ok"
}

FAQ

What happens to any aliases that a profile uses?

  • In case the profile is being aliased, the original profile is removed. As such, all aliases will also return 404.

Can I batch-delete profiles?

  • No, you cannot batch-delete profiles because the batch endpoints only work for events. You have to make one request per the profile that should be deleted. There is no limit on the number of requests you can send at a time.

Tips

  • We highly recommend that you use the profileID that is returned by the Contentful Personalization profile once you have identified the user. Retrieving and updating profiles with this returned ID is more performant than using an ID you have supplied when creating or aliasing a profile.
  • The payload you supply to these endpoints can be sent with the content-type header set as either application/json or text/plain. We recommend that you use text/plain whenever possible to minimize request roundtrip time by avoiding triggering a CORS preflight request. Our SDKs automatically send requests as text/plain.
  • If you want to ensure that a user's location is not resolved as part of a create profile or update profile endpoint request, you can either:
    1. Set an empty string or object, whichever is applicable, to each context.location argument, OR
    2. Not set location or supply location: undefined or location: {} , do not supply location as an option, and set context.library.version to a string that represents a semver of 3.14 or greater.

Types

Response

The returned response of each API endpoint is a data structure indicating the complete representation of the profile(s) and the Experiences & variants that the Experience API has selected for the profile(s). The single-profile endpoints (Create, Update, and Get) return a single profile with experience variant selections and changes. The Batch upsert endpoint returns an array of profiles without variant selections or changes.

/**
 * Single-profile endpoints return this shape inside the response envelope's `data` field.
 */
type ProfileWithSelectedVariants = {
    profile: Profile;
    experiences: SelectedVariantInfo[];
    changes: Change[];
};

Profile

A profile is a holistic representation of a single user. The Contentful Personalization Experience API computes and returns a visitor's profile in response to page, track, identify, screen, and component events. The profile is also used within Experience SDKs to determine the appropriate experience variants and Merge Tag values to render.

type Profile = {
    id: string;
    stableId: string;
    random: number; // Legacy field, not used for variant selection
    audiences: string[];
    traits: Record<string, any>; // Any valid JSON
    location: {
        coordinates?: {
            latitude: number;
            longitude: number;
        } | undefined;
        city?: string | undefined;
        postalCode?: string | undefined;
        region?: string | undefined;
        regionCode?: string | undefined;
        country?: string | undefined;
        countryCode?: string | undefined;
        continent?: string | undefined;
        timezone?: string | undefined;
    };
    session: {
        id?: string;
        isReturningVisitor: boolean;
        landingPage: {
            path: string;
            url: string;
            query: Record<string, string>;
            referrer: string;
            search: string;
        };
        count: number;
        activeSessionLength: number;
        averageSessionLength: number;
    };
    jurisdiction?: 'US' | 'EU';
    stickyVariants?: Record<string, number>; // Maps experienceId to variantIndex
}

Experience

Each response from the Experience API also returns an array of experiences assigned to the profile. You may see this typed in the SDKs as SelectedVariantInfo. Experiences must be published in the content source to be returned on the Experience API. Each experience indicates the variant index (0 = control, 1 = variant 1, etc.) and the content source IDs of content to show.

type Experience = {
    experienceId: string;
    variantIndex: number;
    variants: Record<string, string>;
    sticky?: boolean;
}

type SelectedVariantInfo = Experience; // The type name used in our SDKs

The key of each variants Record is the content source's ID of the baseline or control content. The value of each variant Record is the content source's ID of the applicable variant content. When the variant index is 0, the key and value are the same, otherwise they will be different.

When sticky is true, the variant selection has been locked in for this profile and will not change even if the experience's distribution configuration is modified.

{
    "experiences": [
        { "experienceId": "1", "variantIndex": 0, "variants": {"entryA": "entryA"} },
        { "experienceId": "2", "variantIndex": 1, "variants": {"entryB": "entryC"}, "sticky": true }
    ]
}

Change

The changes array contains inline variable personalizations — where a single keyed value is changed rather than an entire CMS entry being replaced. Each change tells the SDK: for this key, use this value.

type Change = {
    key: string;            // e.g. "heroTitle", "ctaColor"
    type: 'Variable';       // Currently the only type
    value: string | number | boolean | object;
    meta: {
        experienceId: string;
        variantIndex: number;
    };
}
{
    "changes": [
        { "key": "heroTitle", "type": "Variable", "value": "Welcome back!", "meta": { "experienceId": "exp-1", "variantIndex": 1 } },
        { "key": "ctaColor", "type": "Variable", "value": "#ff6600", "meta": { "experienceId": "exp-2", "variantIndex": 2 } }
    ]
}

Event

Each POST endpoint accepts arrays of events as part of requests. Events are the building blocks of profiles; they indicate the actions taken and attributes of individual profiles so those profiles can be segmented into Audiences.

The API accepts five event types: page, track, identify, screen, and component.

type Event = {
  anonymousId: string; // Only when using the batch endpoint, otherwise omitted
  channel: 'web' | 'mobile' | 'server' | 'client'; // 'client' is transformed to 'web'
  context: {
    app?: {  // Optional in API requests
      name: string;
      version: string;
    },
    // Powers `has viewed page` UTM parameter Audience conditions
    campaign?: {
      name: string;
      source: string;
      medium: string;
      term: string;
      content: string;
    },
    // Describes the library making the request
    library: {
      name: string;
      version: string;
    },
    locale?: string; // ISO 639-1 + ISO 3166-2, e.g., "en-US"
    // Optional — the server can resolve location from request headers when the "location" feature is enabled
    location?: {
      coordinates?: {
        latitude: number;
        longitude: number
      },
      city?: string; // Use proper capitalization of the city name
      postalCode?: string;
      region?: string;
      regionCode?: string; // ISO 3166-2
      country?: string;
      countryCode?: string; // ISO 3166-1 alpha-2
      continent?: string;
      timezone?: string;
    },
    // Used for various `has viewed page` Audience conditions
    page?: {
      path: string;
      query: Record<string, string>; // Object of query:value pairs
      referrer: string; // Use full URL including protocol, path, and query string
      search: string; // Full querystring, e.g., "?query=value"
      url: string; // Use full URL including protocol, path, and query string
    }
    userAgent?: string
  }
  messageId: string; // Generate a UUID
  timestamp: Date; // Typically assigned as Date.now()
  type: "page" | "track" | "identify" | "screen" | "component";
  event?: string; // Required when type === "track"
  userId?: string; // Required when type === "identify"
  traits?: Record<string, any>; // Any valid JSON. Required when type === 'identify'
  // Required when type === "page" or type === "track"
  properties?: {
    // The following five properties must be set when type === "page"
    // Not required for events of type === "track"
    path: string;
    query: Record<string, string>; // Object of query:value pairs
    referrer: string; // Use full URL including protocol, path, and query string
    search: string; // Full querystring, e.g., "?query=value"
    url: string; // Use full URL including protocol, path, and query string
    // Any additional properties that can be parsed as valid JSON may be set here
    [key: string]: any;
  }
  // The following fields are only for component view events (type === "component")
  componentId?: string; // Required when type === "component"
  componentType?: 'Entry' | 'Variable';
  experienceId?: string; // Required for sticky variant persistence
  variantIndex?: number; // Required for sticky variant persistence
  viewDurationMs?: number; // Milliseconds
  viewId?: string;
}

Screen events

Screen events (type: "screen") are the mobile equivalent of page events. They require properties.name to be set:

// Required properties for screen events
properties: {
  name: string;
  [key: string]: any;
}

Component view events

Component view events (type: "component") are used primarily to persist sticky variant selections. When both experienceId and variantIndex are set on a component event, the server stores the mapping so the visitor continues seeing the same variant on subsequent requests.

Options

The API endpoints also accept options to modify the behaviour of the request. These are available only on the create profile and update profile endpoints (not the batch endpoints).

type CreateOrUpdateProfilePayload = {
   events: Event[];
   options?: Options;
}

type Options = {
    location?: GeoLocation;
    features?: Feature[];
}

type Feature = "location" | "ip-enrichment"
  • Specifying location in options provides client-side geo-location data that supplements or overrides server-side location resolution.
  • Including "location" in the features array will attempt to resolve the location of the user based on request headers (Cloudflare's location data).
    • If you do not want location to be resolved when sending events, do not add location as a feature option and ensure that context.library.version is set on any accompanying event as a string representing a semver greater than 3.14.
  • Including "ip-enrichment" in the features array will attempt to resolve firmographic data with a connected Albacross API Key. See Albacross for more information.

Traits

traits are arbitrary JSON data set by identify events. Traits are shallow merged together; the most recent value of a given trait key overwrites the prior value.

Traits are useful for custom segmentation using has trait rules. Additionally, traits can serve as snippets for dynamic inline personalizations.

Example for an inline Personalization:

const headline = `Hey ${profile.traits.firstname}`.

type Traits = {
  [key: string]: string | number | boolean | Traits;
};

// example
const traits: Traits = {
  "firstname": "Max",
  "address": {
    "street": "Allee 33"
  },
  "company": "Wayne Enterprises, Inc."
}