# Get custom attribute metadata

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

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

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

## Authorization

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

Retrieves the [metadata properties](https://developer.kustomer.com/docs/apps/klasses#metadata-properties) for the custom attributes of a specific resource (for example, Company, Conversation, Customer, Message, and KObject (custom Object)).

## 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 (coming soon)"
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 (coming soon)"
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 (coming soon)"
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 (coming soon)"
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 (coming soon)"
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 (coming soon)"
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 (coming soon)"
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 |
| --- | --- | --- | --- |
| `page` | integer | no | Page number of results to return. |
| `pageSize` | integer | no | Number of results to return per page. |
| `translate` | string | no | Set to `false` to return untranslated metadata. |
| `include` | string | no | Comma-separated related resources to include when supported by the endpoint. |

## 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!"
        },
        "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 GET \
  --url 'https://api.kustomerapp.com/v1/metadata/customer' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```
