We’ve migrated our documentation to a new site, which means some URLs have changed.
Audience

CDP API Documentation

Overview

The CDP is a software platform that consolidates and manages customer data from various sources in one place. It is used to build a comprehensive customer profile and to personalize customer experiences across multiple channels.

A basic unit of data inside the CDP is a document. There are two kinds of documents in the CDP.

The first one is a profile. It represents your users and stores data related to them.

The second one is an event, which is typically related to a profile. Events can affect data on profiles but are not stored in the CDP. Some examples of events are visit, page view, click, etc.

You can interact with documents using the Document API.

APIs

Introduction

The base URL for the production environment APIs is https://api-cdp.piano.io/.

Making a request

Making a request to our API is as simple as issuing an HTTP request. You can make these HTTP requests using the mechanism of your choosing.

Interpreting a response

All API endpoints will return the HTTP response with a JSON body. There are two kinds of responses: success and error.

Success response

Success response has HTTP status code 200 and a JSON body with the following fields

Field name

Value type

Description

status

Enum

  • SUCCESS

  • ACCEPTED

  • FOUND

SUCCESS - a request is successfully processed
ACCEPTED - a request is accepted and will be processed within the processing queue
FOUND - requested resource is found and returned in response

executionTimeMs

Number

Time needed on the server side to prepare a response

payload (optional)

Object

Contains payload if it’s applicable for a request

For example, a POST call to /api/v3/document might return:

{
    "status": "ACCEPTED",
    "executedAt": "2023-04-05T06:07:41.646Z",
    "executionTimeMs": 10
}

Error responses

Error responses can have the following HTTP status codes: 500, 503, 401, 403, 404. It also has a JSON body with the following fields.

Field name

Value type

Description

status

Enum

  • ERROR

  • NOT_FOUND

ERROR - an error occurred

NOT_FOUND

executionTimeMs

number

Time needed on server side to prepare response

errorType

Enum

  • UNAUTHORIZED

  • FORBIDDEN

  • VALIDATION

  • NOT_FOUND

  • INTERNAL

UNAUTHORIZED - a request does not have proper authorization token
FORBIDDEN - a request have proper authorization token, but it lacks permission to execute this specific operation
VALIDATION - a request is invalid, refer to validationErrors to find out why
NOT_FOUND - requested resource is not found
INTERNAL - internal server error occurred, if it persists, you may need to contact support with errorId from response

errorId

String

Id which can be used by support to track the error

message

String

Short description of the error

validationErrors
(only present if errorType is VALIDATION)

Details object from Joi Validation Error

Containes detailed information why a request is not valid. For instance, what fields is misspelled or missing.

For example, a POST call to /api/v3/document might return:

{
    "status": "ERROR",
    "errorType": "UNAUTHORIZED",
    "errorId": "721cf8dc-aa2e-4f47-b634-b1ef540f7bab",
    "message": "Protected resource, use Authorization header with valid token to get access.",
    "executedAt": "2023-04-05T06:08:41.646Z",
    "executionTimeMs": 0
}

Authentication and Authorization

All APIs require Authentication & Authorization. Bearer authentication (also known as token authentication) is used.

To get access pass a token provided by the support team in the HTTP header named Authorization.
For example, Authorization: Bearer YOUR_TOKEN.

Then based on your token permissions you will be authorized or forbidden to access the API endpoint.

Authentication and Authorization for Audience API users

If you already have an Audience API key available in Audience Insight, you can automatically get CDP API token. If you don’t have an Audience API key yet, you may obtain it by following the documentation on How to obtain one's secret API key in Insight. Once you have an Audience API key, you can exchange it for a CDP API token by making POST HTTP call to /api/v3/tokens/create-or-get-by-cx-api-key with the following body:

{
    "username": "REPLACE_WITH_YOUR_USERNAME",
    "apiKey": "REPLACE_WITH_YOUR_API_KEY"
}

In response you will get:

{
    "status": "SUCCESS",
    "payload": {
        "cdpAuthToken": "YOUR_CDP_AUTH_TOKEN"
    },
    "executedAt": "2025-07-29T16:13:40.427Z",
    "executionTimeMs": 288
}

Then you can use payload.cdpAuthToken as YOUR_TOKEN in requests to the CDP API.

Afterwards, using YOUR_TOKEN you need to make an HTTP GET request to /api/v4/user to get a list of the org units you have access to. You will need to use id of an org unit to work with most CDP APIs like the Document API and others. You will get a response like:

{
    "status": "FOUND",
    "payload": {
        // email to which your token is linked
        "email": "cdp-sa-gitlab+cdp@piano.io",
        // org units available to you 
        "orgUnits": [
            {
                // org unit id
                "id": "631e4141-da18-404f-aed3-b919874b7f00",
                // org unit tags
                // tags define to which cx prefix org unit is linked
                "tags": [
                    "cx-prefix:alx"
                ],
                ...
            },
            {
                "id": "f21033a5-ccdc-43b4-92b6-0d141c476033",
                "tags": [
                    "cx-prefix:fym"
                ],
                ...
            },
            ...
        ],
  ...
}

Pay attention on org unit ids from response, you will need them to do operation with the CDP API.

Authentication and Authorization for non Audience users

Reach out to the Piano Support to request creating of an access token to the CDP API.

Document API

The Document API is used to interact with documents.

Document structure

To understand what is a document in the CDP below are key document attributes.

class

It defines a type of document. It’s typically a Profile or an arbitrary event name. Documents of the same class are treated in a special manner: they are grouped together on a storage layer, providing a consistent view of document properties within inferred data schema.

timestamp

It stores a document’s last updated timestamp.

keys

It is an object with identifiers, which uniquely identify the document.

props

It is an object with document properties. It contains actual document data in a natural JSON manner. Props may also contain namespaced and partitioned properties.

propsTimestamps

It is an object, which stores a timestamp of the last update for each property.

Creating, updating, and deleting documents

Method

POST

Path

/api/v3/document

Overview

The route creates, updates, or deletes documents based on the request parameters.

Making create or update request

Do a HTTP POST call to /api/v3/document.

Body

Let’s start by taking a look at a comprehensive example of a request body and then explain it in detail.

{
    // specify either orgUnitUuid or tags to route data to an org unit
    // orgUnitUuid is recommended option for existing Audience users
    // (orgUnitUuid is an id of org unit from response to GET /api/v4/user request)
    "orgUnitUuid": "631e4141-da18-404f-aed3-b919874b7f00",
    // tags require additional setup by the support team
    // and are designed for narrow use cases
    "tags": [ "org:some_org_id" ],
    
    "class": "Profile",
    "timestamp": "2023-04-05T06:08:41.646Z",
    "keys": {
        "keyName_0": "keyVal_0",
        "keyName_1": [ "keyVal_1_1", "keyVal_1_2" ]
        ...
    },
    "props": {
        "propName_0": "propVal_0",
        "propName_1": {
            "propName_2": "propVal_2"
            ...
        },
        "propName_3:INT": "3",
        "$refName_0": {
            "class": "<TARGET_CLASS>",
            "keys": {
                "targetKeyName_0": "targetKeyVal_0"
                ...								
            }
        },
        
        // namespaced properties
        "namespace_0/propName_0": "propVal_0",
        "namespace_1/": {
            "propName_1": "propVal_1",
        },
        
        // partitioned properties
        "#partitionType1:partitionName1": {
            "propName_0": "propVal_0",
            "propName_1": {
                "propName_2": "propVal_2"
                ...
            },
            
            // namespaces inside partitioned properties
            "namespace_0/propName_0": "propVal_0",
            "namespace_1/": {
                "propName_1": "propVal_1",
            }
        }

        ...
    },
    "propsTimestamps": {
        "propName_0": "2021-12-06T12:34:07.013Z",
        "propName_1": "2021-12-06T12:34:07.013Z",
        "propName_3": "2021-12-06T12:34:07.013Z",
        "$refName_0": "2021-12-06T12:34:07.013Z",
        
         // timestamps of namespaced properties
        "namespace_0/propName_0": "2021-12-06T12:34:07.013Z",
        "namespace_1/": {
            "propName_1": "2021-12-06T12:34:07.013Z",
        },
        
        // timestamps of partitioned properties
        "#partitionType1:partitionName1": {
            "propName_0": "2021-12-06T12:34:07.013Z",
            "propName_1": "2021-12-06T12:34:07.013Z",
            
            // timestamps of namespaces inside partitioned properties
            "namespace_0/propName_0": "2021-12-06T12:34:07.013Z",
            "namespace_1/": {
                "propName_1": "2021-12-06T12:34:07.013Z"
            }
        }
    },
    "propsToDelete": [
        "propName_3",
        "propName_3:INT",
        {
            "name": "propName_3"
        },
        {
            "name": "propName_0",
            "type": "STRING",
            "timestamp": "2021-01-26T16:31:48.722Z",
            "partitionType": "partitionType1",
            "partitionName": "partitionName1"
        },
        "(rR)egexpProp.*"
    ]
}

Make sure to remove all the comments (lines starting with //) and … above before making the request. They are there just to explain the structure of the request body.

Typical response:

{
    "status": "ACCEPTED",
    "executedAt": "2023-04-05T06:07:41.646Z",
    "executionTimeMs": 10
}

Now, let’s go deeper into the details of the example above.

Bulk requests

Not only a JSON object can be passed in a body, but also a JSON array of objects, which means that this route supports both single and bulk operations.

Doing bulk requests is strongly recommended for system integrations.

orgUnitUuid

Identifier of organization unit typicaly received by making GET HTTP request to /api/v4/user.

Tags

This array defines an origin of data. This may include customer id, producer id, integration id (source), etc. Required and optional tag values are provided by the support team on a per-integration basis.

Timestamp

Including a timestamp with each update can help to ensure that the most recent data is being synchronized.

Keys

It’s an object with keys aka identifiers, which uniquely identify the document. It contains 0 or more keys describing a specific object. If no keys are provided then an object can not be referred to individually at any later stage (while still may take part in aggregated data processing). Therefore, submitting objects without keys means that they shall be strictly immutable as no update operation can refer to that object. Giving an object more than one key is okay, this allows for complex identity resolution scenarios. Referring to an object using any of its keys will still refer to the same underlying object. The general rule for defining object keys would be to use actual external domain keys (e.g. id of the object within a source database).

Data types

Data types affect the way how data can be further searched inside CDP. By default in CDP data types of supplied values are automatically inferred.

In certain scenarios, it might be useful to specify property data type explicitly, but putting a corresponding type name after a column in the end property name, for example:

{
    "tags": [ ... ],
    "class": "<CLASS_NAME>",
    "keys": { ... },
    "props": {
        "propA:INT": "35",
        "propB" "35",
        "propC:STRING": 35,
        "propD": 35
    }
}

This update would create 4 properties in the given class, with the following types:

  • propA: integer (although, technically, a string was passed inside a data frame, explicit :INT in the end of the property name will guide CDP to apply INT type here

  • propB: string (wildcard search optimized) is automatically deduced from the passed-in value

  • propC: string (wilcard search optimized), although an integer was passed inside a data frame, explicit :STRING in the end of the property name would guide CDP to apply STRING here

  • propD: integer is automatically deduced from the passed-in value

In case data types are explicitly specified during data ingestion then data type conformity checks are performed by the API layer and errors in conversions (if any) would cause such data updates to not get applied.

The table of all supported data types

Type

Description

Example

INT

Integers
It can only safely store integers in the range -(2^53 − 1) to 2^53 − 1

12

FLOAT

Double-precision 64-bit IEEE 754 floating point numbers.
It is capable of storing positive floating-point numbers between 2^-1074 and 2^1024 as well as negative floating-point numbers between -2^-^1074 and -2^1024.

84.15

BOOLEAN

Boolean

true, false

OBJECT

Objects with arbitrary nested structure. Not indexed for search!

{ "a": { "x" : 1 }, "b": "s" }

DATETIME

Representation of date-time (with millisecond resolution). String with ISO 8601 format. ISO 8601 format should always be used when passing date-time to the CDP.

2021-01-26T16:31:48.722Z

MIXED

Type that allows storing data of any types. Not indexed.

[ "string", 12, { "x" : 1 }]

STRING

Default strings (arbitrary), optimized for wildcard searches

"string"

KW

Keywords (strings of up to 255 chars in length), optimized for exact match searches

"string"

TEXT

arbitrary strings which are automatically tokenized upon indexing (suitable for long texts)

"arbitrary strings which are automatically tokenized"

Keep in mind that CDP supports properties with the same name an different data types, in which case such properties are stored side-by-side.

Nested object

Nested objects within { props } object (e.g. propName_1 above) are supported, but keep in mind that inner properties within nested structures are not indexed for search.

Namespaced properties

Properties supplied to CDP on the top level are called “global” properties meaning they do not belong to any namespace.

Alternatively, a namespace can precede groups of properties to logically isolate certain sets of properties from others. In the case of using namespaces properties with the same name located in different namespaces are processed independently from each other.

Namespaced properties work as well with partitioned properties, in which case namespaces should be specified inside the partition type/name definition.

Partitioned properties

Properties can be partitioned by some type and name for convenience and a few practical reasons. For instance, you may want to separate same properties from different origins to avoid accidental overrides. The same is possible with namespaces, but partitioned properties are more flexible in this regard as they allow you to have both partition type and partition name. Also, partitioned properties save up your property limit. Property with the same name from different partitions counts as a single property. On the other hand, querying partition properties is slower than the global or namespaced properties.

Props to delete

Properties to delete object contains a description of props that should be deleted. It supports two types of syntax short string syntax and extended object syntax. The short syntax can be of two types. The first one is the property name propName, which means deleting all props with this name not taking into account their type. The second one is the property name with a type propName:INT, which means deleting a specific prop with a specific type. Regarding the extended object syntax, it has the same functionality as the short one but also allows to specify deletion timestamp in a field timestamp.
Moreover, the property name can be an RE2 regular expression (more about the syntax here). It means that with propsToDelete: [ '.*'], you can easily delete all props in the document. More than that, you can use a regular expression to achieve a full update functionally for a specific set of props. For example, props that start with some prefix.

References

There's a special type of properties allowed within { props }: references. Reference names start with $ sign. When a reference is encountered, contents of a corresponding sub-object are treated in a special manner: they are used to look up a different object, belonging to <TARGET_CLASS> class and identifiable by $ref: { keys: { ... } } set of keys. Therefore, using references allows for s document linking / relation.

Data limitations

The CDP has the following data limitations:

  • The maximum size of a request body is 10 MegaBytes.

  • The maximum size of a single document is 1 MegaByte.

  • The maximum amount of properties per document class is 800.

  • The maximum length of a property name is 256.

  • The maximum length of a single string property value is 50000 characters.

  • Property names must match the following regexp '^[a-zA-Z0-9\\_\\:\\/\\#\\$\\-]+$' .

Making delete request

Do a HTTP POST call to /api/v3/document.

Body

{
    "tags": ["tag_0", "tag_1"],
    "class": "<CLASS_NAME>",
    "keys": {
        "keyName_0": "keyVal_0"
    },
    "delete": true
}

Typical response:

{
    "status": "ACCEPTED",
    "executedAt": "2023-04-05T06:07:41.646Z",
    "executionTimeMs": 10
}

Getting documents

Method

GET

Path

/api/v3/document

Query parameters:

Parameter name

Value type

Description

orgUnitId

String

Identifier of organization unit

class

String

Document class

keyName

String

Name of a key to use to look for the document

keyValue

String

Value of a key to use to look for the document

format (optional)

Enum

  • SHORT

  • TYPED (default value)

  • FULL

Defines format in which to return document properties

Overview

The route gets a document using provided identifier.

Making a request

Do a HTTP GET call to /api/v3/document?orgUnitId=a2b8c769-e07e-4f62-9651-42b7118d5f9c&class=Profile&keyName=keyName_0&keyValue=keyVal_0.

Typical response:

{
    "status": "FOUND",
    "payload": {
        "keys": {
            "keyName_0": "keyVal_0",
            "keyName_1": [
                "keyVal_1_1",
                "keyVal_1_2"
            ]
        },
        "timestamp": "2023-04-05T06:08:41.646Z",
        "class": "Profile",
        "propsTimestamps": {
            "propName_0:STRING": "2021-12-06T12:34:07.013Z",
            "propName_1:OBJECT": "2021-12-06T12:34:07.013Z",
            "propName_3:INT": "2023-04-05T06:08:41.646Z",
            "$refName_0:REF": "2021-12-06T12:34:07.013Z",
            "namespace_0/": {
                "propName_0:STRING": "2021-12-06T12:34:07.013Z"
            },
            "namespace_1/": {
                "propName_1:STRING": "2021-12-06T12:34:07.013Z"
            },
            "#partitionType1:partitionName1": {
                "propName_0:STRING": "2021-12-06T12:34:07.013Z",
                "propName_1:OBJECT": "2021-12-06T12:34:07.013Z",
                "namespace_0/": {
                    "propName_0:STRING": "2021-12-06T12:34:07.013Z"
                },
                "namespace_1/": {
                    "propName_1:STRING": "2021-12-06T12:34:07.013Z"
                }
            }
        },
        "props": {
            "propName_0:STRING": "propVal_0",
            "propName_1:OBJECT": {
                "propName_2": "propVal_2"
            },
            "propName_3:INT": 3,
            "$refName_0:REF": {
                "class": "<TARGET_CLASS>",
                "keys": {
                    "targetKeyName_0": "targetKeyVal_0"
                }
            },
            "namespace_0/": {
                "propName_0:STRING": "propVal_0"
            },
            "namespace_1/": {
                "propName_1:STRING": "propVal_1"
            },
            "#partitionType1:partitionName1": {
                "propName_0:STRING": "propVal_0",
                "propName_1:OBJECT": {
                    "propName_2": "propVal_2"
                },
                "namespace_0/": {
                    "propName_0:STRING": "propVal_0"
                },
                "namespace_1/": {
                    "propName_1:STRING": "propVal_1"
                }
            }
        }
    },
    "executedAt": "2023-04-06T05:55:20.244Z",
    "executionTimeMs": 2496
}

Last updated: