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 - a request is successfully processed
|
|
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 - an error occurred NOT_FOUND |
|
executionTimeMs |
number |
Time needed on server side to prepare response |
|
errorType |
Enum
|
UNAUTHORIZED - a request does not have proper authorization token
|
|
errorId |
String |
Id which can be used by support to track the error |
|
message |
String |
Short description of the error |
|
validationErrors
|
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 |
|
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:INTin the end of the property name will guide CDP to applyINTtype 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:STRINGin the end of the property name would guide CDP to applySTRINGhere -
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
|
|
|
FLOAT |
Double-precision 64-bit IEEE 754 floating point numbers.
|
|
|
BOOLEAN |
Boolean |
|
|
OBJECT |
Objects with arbitrary nested structure. Not indexed for search! |
|
|
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. |
|
|
MIXED |
Type that allows storing data of any types. Not indexed. |
|
|
STRING |
Default strings (arbitrary), optimized for wildcard searches |
|
|
KW |
Keywords (strings of up to 255 chars in length), optimized for exact match searches |
|
|
TEXT |
arbitrary strings which are automatically tokenized upon indexing (suitable for long texts) |
|
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 |
|
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
|
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
}