# Custom Field API V3 — Developer Guide

- **V2 API reference:** [Custom Field | Sprinklr Developer Portal](https://dev.sprinklr.com/custom-field)


## 1. Overview

A **Custom Field** is a fully customizable property or tag that can be created at several feature levels (for various assets) to organize and categorize content. Custom fields are managed in the Sprinklr UI under **All Settings → Manage Workspace → Custom Fields**, and through this API.

Custom Field V3 exposes create, read, and update on a **single resource path** — `/api/v3/customField` — differentiated by HTTP method:

| Operation | Method | Path |
|  --- | --- | --- |
| Create custom field | `POST` | `/api/v3/customField` |
| Fetch custom field | `GET` | `/api/v3/customField?fieldName=` or `?id=` |
| Update custom field (v2-style merge) | `PUT` | `/api/v3/customField?id=` or `?fieldName=` |
| Update custom field (partial / option edits) | `PATCH` | `/api/v3/customField?id=` or `?fieldName=` |


This is the central design change from V2, which spread the same capabilities across four separate kebab-case paths (`/api/v2/custom-field`, `/api/v2/custom-field/{customFieldName}`, and the option-update endpoint). The V3 change set is:

- Kebab-case paths converted to camelCase (`custom-field` → `customField`).
- Path-based parameters replaced by query parameters, per V3 convention.
- **No changes to request body fields** for `POST` and `PUT` requests.


For endpoint-level reference documentation, see the `Custom Field V3` tag in the V3 OpenAPI specification (`operationId` values `CustomFieldApiV3_getCustomField`, `CustomFieldApiV3_createCustomField`, `CustomFieldApiV3_updateCustomField`, `CustomFieldApiV3_patchCustomFieldOptions`).

### 1.1 The custom field data model

| Layer | Object | What it holds | Notes |
|  --- | --- | --- | --- |
| Identity | `id`, `fieldName`, `label` | Sprinklr ID, API name (`_c_…`), and human-readable label | `fieldName` is generated by Sprinklr on create |
| Definition | `type`, `assetTypes`, `description`, `category`, `enabled` | What the field is and where it applies | `type` is the only **required** property in the schema |
| Options | `values` / `valuesOptions` | Picklist options | Applies only to `PICKLIST` and `PICKLIST_MULTISELECT` |
| Governance | `visibility`, `permissions` | Who sees and who can act on the field |  |
| Behavior | `customFieldControllerById`, `optionType`, `optionKey` | Controlling-field (cascading picklist) configuration |  |
| i18n | `langVsTranslatedFieldValues` | Per-language translated labels/values |  |
| Audit | `createdTime`, `modifiedTime` | Epoch milliseconds | Read-only in practice |


### 1.2 Addressing a custom field

A custom field is addressed in two ways, and the two are **mutually exclusive**:

- **By field name** — `fieldName`, the API name, for example `_c_62fc7b88b893784e3db68fa1`
- **By Sprinklr ID** — `id`, for example `69f021fb6357c2c9885e0eb5`


> Both `fieldName` and `id` on `GET`, `PUT`, and `PATCH`, are *"mutually exclusive with"* the other. The acceptance criteria is `GET` by `?fieldName=` and `PUT`/`PATCH` by `?id=`; both parameters are accepted on all three methods per the specification.


**To find `fieldName` in the UI:** hamburger menu → **All Settings** → **Custom Fields** (under *Manage Workspace*) → three dots next to the field → **Copy Field Name**.

## 2. Base URLs and environments

**All API calls are sent to the production endpoint:**

```
https://api3.sprinklr.com/{env}/api/v3
```

So the custom field resource in production is:

```
https://api3.sprinklr.com/{env}/api/v3/customField
```

Replace `{env}` with your assigned environment identifier (`prod0`, `prod2`, `prod11`, and so on — see [APIs | Sprinklr Developer Portal](https://dev.sprinklr.com/apis) for the environment list).

## 3. Authentication and common headers

All Custom Field API calls are authenticated with OAuth 2.0. See [API Overview](https://dev.sprinklr.com/api-overview) for portal registration, API key and secret generation, and the Authorize flow.

| Header | Value | Purpose | Required on |
|  --- | --- | --- | --- |
| `Authorization` | `Bearer {{accessToken}}` | Authenticates the user with the server | All requests |
| `Key` | `{{apiKey}}` | Authenticates the application with the server | All requests |
| `Content-Type` | `application/json` | Declares the request body media type | `POST`, `PUT`, `PATCH` |
| `Accept` | `application/json` | Declares the acceptable response type | All requests |


## 4. Write operations

### 4.1 Create a custom field

**`POST /api/v3/customField`**

Creates a new custom field. The request body is the `CustomField` schema and is **unchanged from API v2**.

#### Request body

| Parameter | Sub-parameter | Required | Type | Description |
|  --- | --- | --- | --- | --- |
| `type` |  | **Required** | String | Data type of the field. Supported values: `TEXT`, `PICKLIST`, `PICKLIST_MULTISELECT`, `NUMBER`, `DATE`, `TEXT_MULTI`, `TEXTAREA`. The only field marked `required` in the V3 schema. |
| `label` |  | Required (v2 doc) | String | Label or tag for the custom field. |
| `description` |  | Optional | String | Description of the field. Should not exceed 500 characters. |
| `assetTypes` |  | Required (v2 doc) | Array[String] | Asset types the field applies to. See [§8](#8-supported-types-and-asset-types). |
| `values` |  | Optional | Array[`KeyLabelPair`] | Picklist options as `{ "key", "label" }` pairs. |
| `valuesOptions` |  | Optional | Array[`CustomFieldOption`] | Picklist options **with language-translation support**, as `{ "label", "value", "langVsTranslatedFieldValues" }`. Preferred on V3. |
| `category` |  | Optional | String | Category the field falls under. |
| `enabled` |  | Optional | Boolean | If `true`, the custom field is enabled. |
| `visibility` |  | Optional | Object | Visibility of the field. |
|  | `globallyVisible` | Optional | Boolean | If `true`, the field is visible across all users and workspaces. |
|  | `visibilityConfig` | Optional | Array[`AssetShareConfig`] | Sharing configuration. If `visibilityConfig` is defined, `globallyVisible` must be `false`. Each entry: `type` (`CLIENT`, `CLIENT_GROUP`, `USER`, `USER_GROUP`) and `ids`. |
| `permissions` |  | Optional | Array[`Permission`] | Permission levels on the field. |
|  | `spaceType` | **Required** | String | Space type, for example `CLIENT`, `SPACE`. |
|  | `spaceId` | **Required** | String | Space in which the permissions are granted. |
|  | `permissionConfigs` | Optional | Array | Permissions on the asset: `permissions` (List[String]; use `ALL` for all), `type` (`CLIENT`, `CLIENT_GROUP`, `USER`, `USER_GROUP`), `ids`. |
| `optionType` |  | Optional | String | Option type for the custom field, for example `GENERAL`. |
| `optionKey` |  | Optional | String | Option key for the custom field. |
| `customFieldControllerById` |  | Optional | Object | Controlling-field (cascading) configuration, keyed by the controlling custom field's `id`. |
|  | `enableReverseMapping` | Optional | Boolean | Enables reverse mapping on the controller. |
|  | `controllingValues` | Optional | Array[String] | Controlling values of the controller. |
|  | `controllingFieldConfig` | Optional | Map[String, Array[String]] | Maps each controlling value to the controlled values it exposes, for example `{"option 1": ["test1"]}`. |
| `langVsTranslatedFieldValues` |  | Optional | Map[String, Map[String, String]] | Translated fields, keyed by locale, for example `{"es_ES": {"description": "…"}}`. |
| `id`, `fieldName`, `createdTime`, `modifiedTime` |  | — | String / Integer(int64) | Present in the schema; **server-generated**. Do not send on create. |


> **Dev note:** when applying controlling custom field logic, the asset type for both the controlling and the controlled custom field must be the same.


#### Request

```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/customField' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
  "type": "PICKLIST",
  "label": "Buy text books",
  "description": "Text books",
  "assetTypes": [
    "UNIVERSAL_CASE"
  ],
  "valuesOptions": [
    { "label": "Hindi Book",   "value": "Item3" },
    { "label": "English Book", "value": "Item4" },
    { "label": "Maths Book",   "value": "Item5" }
  ],
  "enabled": true,
  "visibility": {
    "globallyVisible": true,
    "visibilityConfig": []
  },
  "permissions": [],
  "optionType": "GENERAL"
}'
```

#### Response — `200 OK`

The response remains wrapped in the `{ "data": { … }, "errors": [] }` envelope, as in V2.

```json
{
  "data": {
    "id": "6a15774f00f392c2e656e68b",
    "fieldName": "_c_6a15774e00f392c2e656e688",
    "label": "Buy text books",
    "description": "Text books",
    "assetTypes": [
      "CASE"
    ],
    "type": "PICKLIST",
    "valuesOptions": [
      { "label": "Hindi Book",   "value": "Item3" },
      { "label": "English Book", "value": "Item4" },
      { "label": "Maths Book",   "value": "Item5" }
    ],
    "enabled": true,
    "visibility": {
      "globallyVisible": true,
      "visibilityConfig": []
    },
    "permissions": [],
    "optionType": "GENERAL",
    "createdTime": 1779791695119,
    "modifiedTime": 1779791695119
  },
  "errors": []
}
```

#### ⚠️ `values` vs `valuesOptions` — read this before you parse the response

Both keys represent picklist options, and they have **different shapes**:

| Key | Item shape | Translation support |
|  --- | --- | --- |
| `values` | `{ "key": "…", "label": "…" }` (`KeyLabelPair`) | No |
| `valuesOptions` | `{ "label": "…", "value": "…", "langVsTranslatedFieldValues": {…} }` (`CustomFieldOption`) | Yes |


Note the inversion: `values[].key` carries the stored value, while `valuesOptions[].value` carries it. Do not map `key` → `label` positionally between the two.

### 4.2 Update a custom field — full (merge) update

**`PUT /api/v3/customField?id={customFieldId}`**

The fields present in the body are merged into the existing custom field. `PUT` **retains full update semantics** and that the request body fields are unchanged from V2.

#### Query parameters

| Parameter | Type | Required | Description | Example |
|  --- | --- | --- | --- | --- |
| `id` | String | Conditional | Custom field ID. Mutually exclusive with `fieldName`. | `69f021fb6357c2c9885e0eb5` |
| `fieldName` | String | Conditional | Custom field API name. Mutually exclusive with `id`. | `_c_62fc7b88b893784e3db68fa1` |


Supply exactly one of the two.

#### Request body — `CustomFieldV3UpdateRequest`

Only these fields are merged into the existing field when present:

| Parameter | Type | Description |
|  --- | --- | --- |
| `valuesOptions` | Array[`CustomFieldOption_metadata`] | Replacement set of picklist options. Item fields: `label`, `value`, `category`, `additional` (Map[String,String]), `langVsTranslatedFieldValues`. |
| `description` | String | Merged into the existing field when present. |
| `category` | String | Merged into the existing field when present. |
| `enabled` | Boolean | Merged into the existing field when present. |
| `assetTypes` | Array[String] | Merged into the existing field when present. |
| `langVsTranslatedFieldValues` | Map[String, Map[String, String]] | Merged into the existing field when present. |


**`type`, `label`, and `fieldName` are not in the update schema.** See the type-change caveat below.

#### Request

```bash
curl --location --request PUT 'https://api3.sprinklr.com/{env}/api/v3/customField?id=69f021fb6357c2c9885e0eb5' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
  "description": "Updated description",
  "category": "Support",
  "enabled": true,
  "assetTypes": [
    "UNIVERSAL_CASE"
  ],
  "valuesOptions": [
    { "label": "Yes",   "value": "Yes" },
    { "label": "No",    "value": "No" },
    { "label": "Maybe", "value": "Maybe" }
  ],
  "langVsTranslatedFieldValues": {
    "es_ES": { "description": "Descripción actualizada" }
  }
}'
```

#### Response

`PUT` responds **`204 No Content` with no response body**.

#### ⚠️ Changing `type` is not supported

Updating the field `type` — for example `PICKLIST` → `PICKLIST_MULTISELECT` — **is not supported**. *"the v2 api did not have support for updating the type. The same behaviour exists here as well."*

**What this means for you:** do not send `type` in an update payload and do not rely on the response status to tell you a type change was rejected. If you need a different type, create a new custom field.

### 4.3 Update a custom field — partial update and option edits

**`PATCH /api/v3/customField?id={customFieldId}`**

Adds new picklist options and deletes existing ones without resending the full option set, and optionally applies the same merge fields as `PUT`. The OpenAPI summary is *"Partial update (picklist options and/or PUT-merge fields)"*.

#### Query parameters

Identical to `PUT`: `id` or `fieldName`, mutually exclusive.

#### Request body — `CustomFieldV3PatchRequest`

| Parameter | Type | Description |
|  --- | --- | --- |
| `addedValuesOptions` | Array[`CustomFieldOption_metadata`] | **Adds** new options to the existing option set. |
| `removedValuesOptions` | Set[String] (`LinkedHashSet<String>`) | **Removes** existing options. |
| `valuesOptions` | Array[`CustomFieldOption_metadata`] | Full option set, as on `PUT`. |
| `description` | String | PUT-merge field. |
| `category` | String | PUT-merge field. |
| `enabled` | Boolean | PUT-merge field. |
| `assetTypes` | Array[String] | PUT-merge field. |
| `langVsTranslatedFieldValues` | Map[String, Map[String, String]] | PUT-merge field. |


`CustomFieldOption_metadata` item fields: `label`, `value`, `category`, `additional` (Map[String,String]), `langVsTranslatedFieldValues`.

#### Request

```bash
curl --location --request PATCH 'https://api3.sprinklr.com/{env}/api/v3/customField?id=69f179317de8c9de2ad70475' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
  "addedValuesOptions": [
    { "label": "Optical1", "value": "test1" },
    { "label": "Optical2", "value": "test2" }
  ],
  "removedValuesOptions": [
    "Item5"
  ]
}'
```

#### Response

`PATCH` responds **`204 No Content` with no response body**.

#### ⚠️ Only `PICKLIST` and `PICKLIST_MULTISELECT` support option updates

**Only `PICKLIST` and `PICKLIST_MULTISELECT` field types support option updates via `PATCH`.**

### 4.4 Method comparison — when to use which

|  | `POST` | `PUT` | `PATCH` |
|  --- | --- | --- | --- |
| Purpose | Create a new custom field | Merge-update the definition | Add/remove options, plus merge fields |
| Addressing | None — identity is generated | `?id=` or `?fieldName=` | `?id=` or `?fieldName=` |
| Body schema | `CustomField` | `CustomFieldV3UpdateRequest` | `CustomFieldV3PatchRequest` |
| Option semantics | Set at creation | `valuesOptions` — whole set | `addedValuesOptions` / `removedValuesOptions` — incremental |
| Can change `type` | Yes (set at creation) | **No** | **No** |
| Success response | `200` + `{data, errors}` | `204`, no body | `204`, no body |


`PUT` and `PATCH` take **different body schemas**. You cannot reuse one payload for both methods.

**Choosing the right option operation:**

| Intent | Operation |
|  --- | --- |
| Append one option to a large picklist | `PATCH` with `addedValuesOptions` |
| Retire a single option | `PATCH` with `removedValuesOptions` |
| Replace the entire option set with an authoritative list | `PUT` (or `PATCH`) with `valuesOptions` |


Using `valuesOptions` where you meant `addedValuesOptions` replaces the whole option set and drops every option you did not resend.

## 5. Read operations

### 5.1 `GET /api/v3/customField` — Fetch a custom field

Fetches custom field details by field name or by ID. Replaces the V2 path-parameter endpoint.

| Parameter | Type | Required | Description | Example |
|  --- | --- | --- | --- | --- |
| `fieldName` | String | Optional | Custom field API name (for example `_c_field`). Mutually exclusive with `id`. | `_c_62fc7b88b893784e3db68fa1` |
| `id` | String | Optional | Custom field ID. Mutually exclusive with `fieldName`. | `6a15774f00f392c2e656e68b` |


```bash
# By field name — the primary lookup per IN-12885
curl --location 'https://api3.sprinklr.com/{env}/api/v3/customField?fieldName=_c_62fc7b88b893784e3db68fa1' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'
```

```bash
# By ID
curl --location 'https://api3.sprinklr.com/{env}/api/v3/customField?id=6a15774f00f392c2e656e68b' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'
```

#### Response — `200 OK`

The OpenAPI document types the `GET` `200` response as an **array of `CustomField`**, whereas `POST` returns a single `CustomField`. Handle the read response as a collection even when you addressed a single field.

```json
{
  "data": [
    {
      "id": "62fc7b88b893784e3db68fa6",
      "fieldName": "_c_62fc7b88b893784e3db68fa1",
      "label": "profile check",
      "assetTypes": [
        "PROFILE"
      ],
      "type": "PICKLIST_MULTISELECT",
      "values": [
        { "key": "Tag",  "label": "Tag" },
        { "key": "Tag1", "label": "Tag1" }
      ],
      "category": "",
      "enabled": true,
      "visibility": {
        "globallyVisible": true,
        "visibilityConfig": []
      },
      "permissions": []
    }
  ],
  "errors": []
}
```

## 6. Response format and status codes

### 6.1 The V3 envelope

`POST` responses use the two-part envelope:

```json
{
  "data": { },
  "errors": []
}
```

| Field | Type | Description |
|  --- | --- | --- |
| `data` | Object (or Array on `GET`) | The custom field object(s) |
| `errors` | Array[Error] | Array of error objects; empty when there are no errors |


`PUT` and `PATCH` return **no body**.

### 6.2 Response codes

| HTTP code | Method | Scenario |
|  --- | --- | --- |
| `200 OK` | `GET`, `POST` | Success. Body contains the custom field. |
| `204 No Content` | `PUT`, `PATCH` | Success. No response body. |
| `400 Bad Request` | All | Invalid parameters, invalid body, or an unsupported operation such as an option edit on a non-picklist type. |
| `401 Unauthorized` | All | Invalid or missing `Authorization` token. |
| `403 Forbidden` | All | Insufficient permissions on the custom field or workspace. |
| `404 Not Found` | All | No custom field matches the supplied `fieldName` or `id`. |


## 7. V2 → V3 migration

### 7.1 Endpoint mapping

| V2 | V3 |
|  --- | --- |
| [`POST /api/v2/custom-field`](https://dev.sprinklr.com/create-custom-field) (create) | `POST /api/v3/customField` |
| [`GET /api/v2/custom-field/{customFieldName}`](https://dev.sprinklr.com/fetch-custom-field-using-field-name) (fetch) | `GET /api/v3/customField?fieldName=` |
| Update Custom Field | `PUT /api/v3/customField?id=` |
| [Update Options with Label](https://dev.sprinklr.com/update-options-with-label) (assign a new value or delete an existing value for a given custom field name) | `PATCH /api/v3/customField?id=` with `addedValuesOptions` / `removedValuesOptions` |


The single biggest structural change: V2 used **path** parameters on kebab-case paths (`/custom-field/{customFieldName}`); V3 uses **query** parameters on one camelCase path (`/customField?fieldName=`).

### 7.2 What changed and what did not

| Aspect | API V2 | API V3 | Impact |
|  --- | --- | --- | --- |
| Path casing | `custom-field` | `customField` | Update every URL |
| Field addressing | Path parameter `{customFieldName}` | Query parameter `?fieldName=` or `?id=` | Update URL construction; `id` lookup is new |
| Create request body | `CustomField` | `CustomField` — **unchanged** | No payload migration needed for `POST` |
| Update request body | v2 merge fields | `CustomFieldV3UpdateRequest` — **same fields** | No payload migration needed for `PUT` |
| Update response | V2 response | `204`, no body | Stop parsing the update response; treat 2xx as success |
| Option edits | Separate "Update Options with Label" endpoint | `PATCH` on the same resource | Consolidate to one path and one method |
| Option representation | `values` (`key`/`label`) | `values` **and** `valuesOptions` (`value`/`label` + translations) | Prefer `valuesOptions` for i18n |
| Create response option key | `values` | Echoes the key you sent (`valuesOptions` in, `valuesOptions` out) — fixed under IN-13195 | Parse defensively |
| Envelope | `{data, errors}` | `{data, errors}` — **unchanged** for `POST` | No change |


## 8. Supported types and asset types

### 8.1 Custom field types

| Type | Description |
|  --- | --- |
| `PICKLIST` | Select only one value from the available options. |
| `PICKLIST_MULTISELECT` | Select more than one value from the available options. |
| `NUMBER` | Value in numerical form — phone number, employee ID, and similar. |
| `DATE` | A calendar picker; for example purchase date, complaint date, completion date. |
| `TEXT` | Text details — name, customer ID, description. |
| `TEXTAREA` | Same as text but holds greater length. |
| `TEXT_MULTI` | Like a multi-picklist, but no values are defined at creation; options are created on the go. |


Only `PICKLIST` and `PICKLIST_MULTISELECT` accept option definitions and option updates.

### 8.2 Supported asset types

`ACCOUNT`, `OUTBOUND_MESSAGE`, `MESSAGE`, `PROFILE`, `MEDIA_ASSET`, `USER`, `CAMPAIGN`, `SUB_CAMPAIGN`, `COMMUNITY`, `PRODUCT`, `PAID_INITIATIVE`, `AD_SET`, `AD_VARIANT`, `CASE`, `UNIVERSAL_CASE`, `SURVEY`, `TASK`.

## 9. Use cases

### 9.1 Provision a case-tagging picklist from your deployment pipeline

Issue a single `POST /api/v3/customField` with `type: "PICKLIST"`, the target `assetTypes`, and the full `valuesOptions` set. Capture `data.fieldName` (`_c_…`) from the response and store it — it is the addressing key for every later read and update, and it is also the key you use when writing values onto a profile or case.

### 9.2 Add one option to a live picklist without disturbing the rest

Do not `GET`, mutate, and `PUT` the whole option list — a concurrent editor's option would be lost. Use:

```json
{ "addedValuesOptions": [ { "label": "Priority Support", "value": "priority_support" } ] }
```

`PATCH` takes a distributed lock on the field (added under IN-13211), so concurrent adds do not clobber each other.

### 9.3 Retire an option that is no longer offered

```json
{ "removedValuesOptions": ["legacy_plan"] }
```

Removing an option does not delete the field. If you need the whole field out of circulation, `PATCH` with `{"enabled": false}` instead — there is no delete endpoint.

### 9.4 Localize a picklist for a new market

Translations live in `langVsTranslatedFieldValues`, at both field level and option level:

```json
{
  "valuesOptions": [
    {
      "label": "Hindi Book",
      "value": "Item3",
      "langVsTranslatedFieldValues": {
        "es_ES": { "label": "Libro de hindi" }
      }
    }
  ],
  "langVsTranslatedFieldValues": {
    "es_ES": { "description": "Descripción actualizada" }
  }
}
```

This is exactly why `valuesOptions` exists alongside `values`: `KeyLabelPair` has no translation slot.

### 9.5 Build a cascading (controlling) picklist

Set `customFieldControllerById`, keyed by the **controlling** field's `id`, on the **controlled** field:

```json
{
  "customFieldControllerById": {
    "66b0b924b8f96b59e3003ce3": {
      "controllingFieldConfig": {
        "option 1": ["test1"],
        "option 2": ["test2"]
      }
    }
  }
}
```

When *option 1* is selected on the controlling field, only `test1` is offered on this field. Both fields must share the same asset type.

### 9.6 Resolve a `fieldName` you copied from the UI

`GET /api/v3/customField?fieldName=_c_62fc7b88b893784e3db68fa1` returns the definition, including `type` — check it before sending option edits, since only `PICKLIST` and `PICKLIST_MULTISELECT` accept them.

### 9.7 Reconcile a picklist against an authoritative external list

When your system owns the complete option set (for example, a product catalog), compute the delta and send one `PATCH`:

```json
{
  "addedValuesOptions": [
    { "label": "Model X2", "value": "model_x2" }
  ],
  "removedValuesOptions": ["model_w1"]
}
```

A delta `PATCH` is safer than a wholesale `valuesOptions` replacement because it leaves options added by other systems intact.

## 10. Caveats and best practices

**Method selection**

- `PUT` and `PATCH` take different body schemas. Payloads are not interchangeable.
- Use `PATCH` for incremental option work; reserve full `valuesOptions` replacement for when you own the entire list.
- There is no delete. Plan for `enabled: false` as the retirement mechanism.


**Identifiers**

- `fieldName` and `id` are mutually exclusive on `GET`, `PUT`, and `PATCH`. Send exactly one.
- `fieldName` values are prefixed `_c_`. Copy them from the create response or from the UI (**Copy Field Name**).
- Send option `value` / `key` as strings, not numbers ([IN-13205](https://sprinklr.atlassian.net/browse/IN-13205)).


**Types**

- `type` cannot be changed after creation. Create a new field instead.
- Option fields are meaningful only for `PICKLIST` and `PICKLIST_MULTISELECT`.


**Response handling**

- Treat `200` (read/create) and `204` (update) both as success; do not assert on a single status code.
- Do not attempt to parse an update response body — there is none.
- Accept both `values` and `valuesOptions` when reading options, and mind the `key` ↔ `value` inversion.
- Do not assume `assetTypes` round-trips exactly as sent.


**Governance**

- If `visibilityConfig` is populated, `globallyVisible` must be `false`.
- `permissions[].spaceType` and `permissions[].spaceId` are both required whenever a `Permission` entry is supplied.


**Constraints carried from V2**

- `description` should not exceed 500 characters.
- A controlling field and its controlled field must share the same asset type.


*All JSON payloads in this guide are illustrative examples. They are not real customer data and are not guaranteed production responses. All credentials are placeholders (`{{accessToken}}`, `{{apiKey}}`) and must never be committed or logged.*