# Update custom attribute metadata

> PUT /v1/metadata/{resource} · Core resources API

Source: https://developer.kustomer.com/api/core-resources/update-custom-attribute-metadata

```http
PUT https://{orgname}.api.kustomerapp.com/v1/metadata/{resource}
Authorization: Bearer <API key>
```

## Authorization

Requires any one of these roles: `org.permission.metadata.update`; or legacy `org.admin.metadata.write`. The API key or user must hold at least one of them. Legacy roles predate permission sets and still work.

Sets the [metadata properties](https://developer.kustomer.com/docs/apps/klasses#metadata-properties) for custom attributes on any of the available resources (for example, Company, Conversation, Customer, Message, and KObject (custom Object)).

Metadata property options are dependent on the data type as specified by the
suffix of the property name. The `displayName` default value is set to the title cased version of the camelCase property name and the type is determined automatically. For example, a property named `totalOrdersNum` will have a default `displayName` value of `Total Orders` (if no alternative `displayName` value is set) and the type is set to `number` based on the `Num` suffix in the property name `totalOrdersNum`.

To learn more about options for metadata properties, visit [Klasses](https://developer.kustomer.com/docs/apps/klasses#metadata-properties).

### Note
> When you configure metadata property types for custom attributes, you must configure metadata for custom properties ending with `Id` before you can use the attributes. When you update these properties, Kustomer validates these values to ensure that the related object exists and matches the specified target type.

> When configuring attributes as sensitive, note that some attributes will inherit any permissions and metadata from other attributes. These are
  - `customer.externalId` inherits permissions and metadata from `customer.externalIds`
  - `customer.firstName`, `customer.lastName`, and `customer.displayName` inherits permissions and metadata from `customer.name`

### Custom Attributes
[Custom attributes](https://help.kustomer.com/define-attributes-Skr924HI) are optional and allow you to specify custom key-value pairs.

Kustomer uses a naming convention for custom attributes to specify the field data type with a name suffix:

#### Naming Suffix

* `Num` *number* or *null*
* `At` *date-time* or *null*
* `Str` *string* or *null*, length: [ 0 .. 1024 ] characters
* `Txt` *string* or *null*, length: [ 0 .. 1024 ] characters
* `Bool` *boolean* or *null*
* `Url` *string* or *null*, format: *uri*

## About Custom attributes

Custom attributes are your own fields on Kustomer objects, such as a loyalty tier on a customer or an order number on a conversation. Their metadata tells Kustomer each field's label, type, allowed values and sensitivity.

Every customer, company, conversation, message, note, user, KObject and tracked event can carry custom attributes in its `custom` object, and the suffix of each attribute's name sets its type. Metadata is the per-object-type configuration that makes these fields usable in Kustomer. It gives each attribute the label agents see in the agent desktop, turns a string into a dropdown of allowed values, links an ID to another object, or marks a field as sensitive. Admins edit the same metadata in Kustomer's settings.

#### Naming conventions

Attribute names are camelCase and end in a type suffix:

| Suffix | Type | Example |
| --- | --- | --- |
| `Str` | String, up to 1,024 characters | `loyaltyTierStr` |
| `Txt` | Text, shown as a multi-line field | `notesTxt` |
| `Num` | Number | `lifetimeValueNum` |
| `At` | Date and time | `lastOrderAt` |
| `Bool` | Boolean | `marketingOptInBool` |
| `Url` | URI | `accountUrl` |
| `Id` | Reference to another object | `primaryOrderId` |

Every type also accepts `null`. Keep names stable: the API, workflows and search use the system name (search uses forms like `customer_custom_loyaltyTierStr`), even if an admin later changes the display name.

#### Metadata resources and properties

The `resource` path parameter picks which object type's attributes you are configuring: `customer`, `company`, `conversation`, `message`, `note`, `user`, `kobject`, `kobject.<klassName>` for one Klass, `event`, or `event.<suffix>`. The body's `properties` maps attribute names to their settings: `displayName` (defaults to the title-cased name without its suffix, so `totalOrdersNum` becomes "Total Orders"), `enumeration` for allowed values, `target` for what an `Id` attribute points to, and `sensitive` to mask the value. `standardProperties` configures built-in attributes the same way, for example to mark them sensitive.

An attribute ending in `Id` must have metadata before you write values to it, because Kustomer checks that each value refers to an existing object of the target type. Some standard attributes inherit sensitivity from others: `customer.externalId` follows `customer.externalIds`, and `customer.firstName`, `customer.lastName` and `customer.displayName` follow `customer.name`.

#### Which endpoint to use

| To | Call |
| --- | --- |
| Read the attribute configuration for an object type | [`GET /v1/metadata/{resource}`](https://developer.kustomer.com/api/core-resources/get-custom-attribute-metadata) |
| Add or change attribute labels, types, allowed values or sensitivity | [`PUT /v1/metadata/{resource}`](https://developer.kustomer.com/api/core-resources/update-custom-attribute-metadata) |

`PUT` merges: it only touches the attributes you include, merging your settings into what is already there, and sending `null` for an attribute removes its metadata. One request can set up to 100 `properties`. Pass `translate=false` to read display names without translation. Each object type has its own metadata, so configure `customer` and `conversation` attributes with separate requests.

Related: [Custom attributes concepts](https://developer.kustomer.com/docs/concepts/custom-attributes), [Klasses](https://developer.kustomer.com/api/core-resources#klasses), [Customer unmasking windows](https://developer.kustomer.com/api/core-resources#customers-unmasking-windows)

### Recipes

#### 1. Add a dropdown attribute to customers and fill it in

Give customers a field from your system, such as a loyalty tier, with a fixed list of values agents can pick from, then set it from your sync.

**Step 1.** [`PUT /v1/metadata/{resource}`](https://developer.kustomer.com/api/core-resources/update-custom-attribute-metadata.md). Define the attribute on the `customer` resource. The `Str` suffix makes it a string; `enumeration` lists the allowed values and shows agents a dropdown; `displayName` is the label. Other attributes are left as they are.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/metadata/customer" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "properties": {
      "loyaltyTierStr": { "displayName": "Loyalty tier", "enumeration": ["bronze", "silver", "gold"] }
    }
  }'
```

```bash title="kusty"
kusty api PUT /v1/metadata/customer \
  --field properties.loyaltyTierStr.displayName="Loyalty tier" \
  --field 'properties.loyaltyTierStr.enumeration:=["bronze","silver","gold"]'
```

**Step 2.** [`PUT /v1/customers/{id}`](https://developer.kustomer.com/api/core-resources/update-customer.md). Write the value into the customer's `custom` object under the same name. Search, workflows and the agent desktop all use the system name `loyaltyTierStr`, whatever the label says.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/customers/$CUSTOMER_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"custom": {"loyaltyTierStr": "gold"}}'
```

```bash title="kusty"
kusty api PUT /v1/customers/$CUSTOMER_ID --field custom.loyaltyTierStr=gold
```

> To change the label or the values later, send just that attribute again. Sending `null` for it removes its metadata. Search for it as `customer_custom_loyaltyTierStr`; see [search](https://developer.kustomer.com/docs/concepts/search).

#### 2. Link conversations to a custom object

Add a reference from conversations to a KObject, such as the order a conversation is about, so agents and workflows can use the order's data.

**Step 1.** [`PUT /v1/metadata/{resource}`](https://developer.kustomer.com/api/core-resources/update-custom-attribute-metadata.md). Create an `Id` attribute on conversations with a `target` of `kobject.<klass>`. This metadata must exist before you write any values, and `target` can't be changed later, so check the Klass name first.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/metadata/conversation" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"properties": {"orderId": {"displayName": "Order", "target": "kobject.ecomm-order"}}}'
```

```bash title="kusty"
kusty api PUT /v1/metadata/conversation \
  --field properties.orderId.displayName=Order \
  --field properties.orderId.target=kobject.ecomm-order
```

**Step 2.** [`GET /v1/klasses/{name}/externalId={externalId}`](https://developer.kustomer.com/api/core-resources/get-kobject-by-external-id.md). Find the KObject to link, for example from an order number the customer mentioned. Use its Kustomer `id`, not your external ID.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/klasses/ecomm-order/externalId=$ORDER_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
KOBJECT_ID=$(kusty api GET "/v1/klasses/ecomm-order/externalId=$ORDER_ID" | jq -r '.data.id')
```

**Step 3.** [`PUT /v1/conversations/{id}`](https://developer.kustomer.com/api/core-resources/update-conversation.md). Set the attribute on the conversation. Kustomer checks that the ID belongs to an existing `ecomm-order` KObject and rejects the update otherwise.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/conversations/$CONVERSATION_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"custom\": {\"orderId\": \"$KOBJECT_ID\"}}"
```

```bash title="kusty"
kusty api PUT /v1/conversations/$CONVERSATION_ID --field custom.orderId=$KOBJECT_ID
```

> For a list of references, set `multi: true` when you first create the attribute; it can't be changed afterwards. The [orders integration guide](https://developer.kustomer.com/docs/guides/kobject-orders-integration) builds this end to end.

#### 3. Check an object type's attributes before syncing

Read which custom attributes exist, and which values they allow, so your sync sends valid fields instead of failing record by record.

**Step 1.** [`GET /v1/metadata/{resource}`](https://developer.kustomer.com/api/core-resources/get-custom-attribute-metadata.md). Read the metadata for the object type you write to. `properties` maps each attribute's system name to its `type`, label and, for dropdowns, its `enumeration`. Pass `translate=false` to get the labels as configured rather than translated.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/metadata/customer?translate=false" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/metadata/customer --query translate=false > customer-metadata.json
```

**Step 2.** Build a lookup of attribute names to allowed values, and check your outgoing records against it. Attributes without metadata can still be written as long as their names end in a valid suffix, except `Id` attributes, which need metadata first.

```bash title="kusty"
jq -r '.data.attributes.properties | to_entries[] | [.key, .value.type, ((.value.enumeration // []) | join("|"))] | @tsv' customer-metadata.json
```

### Example object

A real record from [`GET /v1/metadata/{resource}`](https://developer.kustomer.com/api/core-resources/get-custom-attribute-metadata.md):

```json
{
  "type": "metadata",
  "id": "customer",
  "attributes": {
    "properties": {
      "shopifyCustomerIdNum": {
        "type": "number",
        "displayName": "Shopify Customer Id"
      },
      "myCustomStr": {
        "modifiedBy": "57fd2d309d0ef811009cfdd6",
        "modifiedAt": "2016-11-08T12:53:21.532Z",
        "updatedAt": "2016-11-08T12:53:21.532Z",
        "type": "string",
        "enumeration": [
          "option one",
          "option two",
          "option three"
        ],
        "displayName": "My Custom String!"
      },
      "ordersId": {
        "modifiedBy": "57fd2d309d0ef811009cfdd6",
        "modifiedAt": "2016-11-08T12:53:21.532Z",
        "updatedAt": "2016-11-08T12:53:21.532Z",
        "type": "string",
        "target": "kobject.order",
        "multi": true,
        "displayName": "Orders"
      }
    }
  },
  "relationships": {
    "org": {
      "links": {
        "self": "/v1/orgs/57f29863a1dbf61100e6aa92"
      },
      "data": {
        "type": "org",
        "id": "57f29863a1dbf61100e6aa92"
      }
    }
  },
  "links": {
    "self": "/v1/metadata/customer"
  }
}
```

Fields:

- `id` (string): The object type this metadata configures, such as `customer` or `kobject.order`. It's the same value as the `{resource}` path parameter.
- `attributes.properties` (object): Your custom attributes, keyed by their system names including the type suffix. Only attributes that have metadata appear. `PUT` merges into this map, and sending `null` for a name removes that attribute's metadata.
- `attributes.properties.shopifyCustomerIdNum` (object): An attribute with only a label set. Its `Num` suffix makes it a number, so this is all the metadata it needs.
- `attributes.properties.shopifyCustomerIdNum.type` (string): Set automatically from the name's suffix, such as `number` for `Num` and `string` for `Str`. You don't need to send it.
- `attributes.properties.shopifyCustomerIdNum.displayName` (string): The label agents see. If you don't set one, it's the title-cased name without its suffix, so `totalOrdersNum` becomes "Total Orders".
- `attributes.properties.myCustomStr.modifiedBy` (string): The ID of the user who last changed this attribute's metadata. Kustomer sets it, with `modifiedAt` and `updatedAt`.
- `attributes.properties.myCustomStr.enumeration` (array): The allowed values. Agents get a dropdown instead of a free-text field.
- `attributes.properties.ordersId` (object): An `Id` attribute, a reference to other objects. It needs metadata with a `target` before you can write values, and Kustomer checks that every value is the ID of an existing object of that type.
- `attributes.properties.ordersId.target` (string): The type of object the IDs point to, such as `kobject.order`. It can't be changed once set.
- `attributes.properties.ordersId.multi` (boolean): `true` when the attribute holds a list of IDs instead of one. Like `target`, it can't be changed once set.
- `links.self` (string): The metadata endpoint for this object type. Each object type has its own metadata.

### Permissions

Fewest roles for a read-only integration: `org.permission.metadata.read`. For every endpoint: `org.permission.metadata.read`, `org.permission.metadata.update`.

## Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `resource` | string | yes | Resource for custom attribute metadata retrieval. Valid values: `company`, `conversation`, `customer`, `message`, `note`, `user`, `kobject`, `kobject.<klassSlug>`, `event`, and `event.<suffix>`. |

## Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `translate` | string | no | Set to `false` to return untranslated metadata. |

## Request body

Content type: `application/json` (required)

- `standardProperties` (object)
- `relationships` (object)
- `properties` (object)
- `validations` (array<string<object-id>>): (max items 10; unique items)

Example:

```json
{
  "standardProperties": {},
  "relationships": {},
  "properties": {},
  "validations": [
    "string"
  ]
}
```

## Responses

### 200

OK

- `data` (object, required)
  - `type` (string, required)
  - `id` (string, required)
  - `attributes` (object, required)
    - `properties` (object, required)
  - `relationships` (object, required)
    - `org` (object, required)
  - `links` (object, required)
    - `self` (string, required)

```json
{
  "data": {
    "type": "metadata",
    "id": "customer",
    "attributes": {
      "properties": {
        "shopifyCustomerIdNum": {
          "type": "number",
          "displayName": "Shopify Customer Id"
        },
        "myCustomStr": {
          "modifiedBy": "57fd2d309d0ef811009cfdd6",
          "modifiedAt": "2016-11-08T12:53:21.532Z",
          "updatedAt": "2016-11-08T12:53:21.532Z",
          "type": "string",
          "enumeration": [
            "option one",
            "option two",
            "option three"
          ],
          "displayName": "My Custom String Enum!"
        },
        "ordersId": {
          "modifiedBy": "57fd2d309d0ef811009cfdd6",
          "modifiedAt": "2016-11-08T12:53:21.532Z",
          "updatedAt": "2016-11-08T12:53:21.532Z",
          "type": "string",
          "target": "kobject.order",
          "multi": true,
          "displayName": "Orders"
        }
      }
    },
    "relationships": {
      "org": {
        "links": {
          "self": "/v1/orgs/57f29863a1dbf61100e6aa92"
        },
        "data": {
          "type": "org",
          "id": "57f29863a1dbf61100e6aa92"
        }
      }
    },
    "links": {
      "self": "/v1/metadata/customer"
    }
  }
}
```

### 400

Bad Request

### 401

Unauthorized

### 403

Forbidden

### 404

Not Found

## Example request

```bash
curl --request PUT \
  --url 'https://api.kustomerapp.com/v1/metadata/customer' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "standardProperties": {},
  "relationships": {},
  "properties": {},
  "validations": [
    "string"
  ]
}'
```
