Contact properties
Declare the custom fields contacts carry in attributes, with a display name and a type that incoming values are coerced to.
A contact property declares one custom field: a key, a display name and a type. The values themselves live on each contact, in its attributes object under that key (see Contacts). Declaring the property is what gives those values a type. Without one, a value imported as "42" stays a string, and a segment filter such as gt with the number 9 never matches it, because numbers and strings are not compared with each other (and with the string "9" it compares as text, so "42" is not greater).
The type is applied when a value is written, not afterwards:
| Type | What a written value becomes |
|---|---|
string | The value as a string. |
number | A number. A numeric string is converted, true and false (and the strings "true", "yes", "false" and "no", in any case) become 1 and 0, and anything else becomes 0. |
boolean | true for true, 1, or the strings "true", "1" and "yes" in any case, with surrounding spaces ignored; false for anything else. |
date | The value as a string. It is not parsed or checked. Send ISO 8601 dates so that gt and lt, which compare strings, order them correctly. |
Coercion happens when contacts are added or imported, and when a contact is updated with PATCH /v1/contacts/{id}. For a declared property, the same JSON value is stored the same way on every one of those paths. For a key with no declared property they differ: adding or importing contacts through the API creates the property for you, with type string and the key as its name, so 5 is stored as "5", while PATCH stores the value exactly as sent and creates no property. Attribute keys only have to be non-empty, free of . and null characters, and not start with $; they do not have to follow the letters, digits and underscores rule that creating a property enforces, so an attribute key such as sign-up source creates a property whose key this endpoint would reject. Declare number and boolean properties before importing, so values arrive typed.
Changing a property's type later does not convert the values already stored on contacts.
The contact property object
Every field is always present.
| Field | Type | Description |
|---|---|---|
id | string | The property's id, a UUID. Nothing in the API takes it; properties are addressed by key. |
key | string | The key used in a contact's attributes and in segment filters. |
name | string | Display name. |
type | "string" | "number" | "boolean" | "date" | null | How values are coerced. null only on a property whose type an earlier version of this endpoint cleared, when an update left type out; a null type stores values as strings. Send type to set it again. |
created_at | string (ISO 8601) | When the property was created. |
List properties
GET /v1/contact-properties · requires contacts:read
Every property declared in the workspace, sorted by name, in the list envelope. The list is not paged and not capped: every property is in data, has_more is always false and next_cursor is always null.
Example
Response
200 OK
Create or update a property
POST /v1/contact-properties · requires contacts:write
Declares a property, keyed on key. When no property with that key exists, it is created with 201. When one already exists, its name is replaced, and its type too when you send one, and the call answers 200 with the updated property, so the call is safe to repeat. Leaving type out keeps the existing property's type, or uses string for a new one.
Keys are matched exactly, including case: Plan and plan are two different properties.
Body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
key | string | yes | Letters, digits and underscores only. At least one character. | |
name | string | yes | Display name, 1 to 120 characters. | |
type | "string" | "number" | "boolean" | "date" | no | "string" on create; unchanged on update | How values are coerced. |
Example
Response
201 Created for a new property, 200 OK when the key already existed. Either way the body is the property object.
Errors
| Status | Type | When |
|---|---|---|
| 422 | invalid_request | key is missing or has characters other than letters, digits and underscores, name is missing, empty or longer than 120 characters, or type is not one of the four (message Validation failed, with details). |
| 409 | conflict | Another request declared the same key at the same moment and the write could not be completed. The property exists now; retry to update it. |
Delete a property
DELETE /v1/contact-properties/{key} · requires contacts:write
Removes the property's definition. The values stay on every contact that has them, in attributes, and segment filters on the key keep working, because they read the contact's attributes directly. Only the declaration goes: new values for the key are no longer coerced, and adding or importing contacts through the API with that key creates the property again as string.
A key that no property in the workspace has answers 404 not_found, so a typo in the key is not reported as a deletion.
Path parameters
| Parameter | Description |
|---|---|
key | The property's key, matched exactly. |
Example
Response
200 OK
Errors
| Status | Type | When |
|---|---|---|
| 404 | not_found | No property with that key in this workspace. The message names the key. Nothing is changed. |