/v1/custom-fields/{custom_field_id}betaUpdate a custom field
- Needs
- Create and edit contacts, tags, contact groups and custom fields
(crm:write) - Plan
- Any plan with API access
- Limits
- 60 a minute per key
- Dry run
- Yes — every check runs with ?dry_run=true, nothing changes
- Undo
- Send the old values back (rename_options with the old labels). Removed options can be added again with the same values.
Changes only what you pass: label, type, the options of a choice field, placeholder, read_only and visibility. Everything else stays as it is.
- The `key` never changes: contacts' values, chatbots and messages use it. Sending a different
keyis400(reason: key_immutable); create a new field instead. - Options:
add_optionsappends choices (strings, or{label, value}; values unique),rename_optionsgives existing options a new label byvalue(the value, and every stored answer, stays),remove_optionstakes options out. A choice field keeps at least one option, and at most 500. - While contacts hold values (
in_use: true) thetypecannot change (409,reason: type_change_in_use: the values would be hidden) and options cannot be removed (409,reason: option_in_use): relabel them instead. On an unused field a new choice type needsadd_options, and a non-choice type drops the options. - Checkbox, boolean and choice fields have no placeholder.
- Labels are unique: a label another field has is
409(reason: duplicate_label).
Only managers can change custom fields. A request that changes nothing writes nothing and answers changed: []: adding an option exactly as it already is and removing a value that is not there change nothing, so sending the same PATCH again is safe. If the field is changed elsewhere (in the panel, say) while your request runs, nothing is written and the answer is 409 (reason: changed_concurrently): read the field again and send your change against what it is now. Contacts show the new label on their next read. Try it with ?dry_run=true: every check runs and nothing changes.
Try it
Protects against doing it twice if you retry: a retry with the same key gets the first answer back instead of running again.
Code and response
curl -X PATCH 'https://mcp.wa-api.cloud/v1/custom-fields/32?dry_run=true' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"label": "Loyalty level",
"add_options": [
"platinum"
],
"rename_options": [
{
"value": "gold",
"label": "Gold ✨"
}
]
}'The code reads your key from $API_KEY.
Parameters
| Field | Type | What it is |
|---|---|---|
| custom_field_idrequired | string | integer · path | The custom field id.pattern ^[1-9]\d{0,18}$ |
Body
| Field | Type | What it is |
|---|---|---|
| key | string | Optional: the field's current key, to make sure you change the right field. It cannot change.1–64 characters |
| label | string | New label (any language).1–255 characters |
| type | string | New type. Only while no contact holds a value.one of: text, textarea, select, multiselect, checkbox, radio, date, datetime, number, email, url, phone, int, float, boolean, decimal |
| add_options | array of string | object | Choices to add: strings (label = value) or {label, value}.0–100 items |
| rename_options | array of object | {value, label}: a new label for the option with this value.0–100 items |
| valuerequired | string | 1–255 characters |
| labelrequired | string | 1–255 characters |
| remove_options | array of string | Option values to remove. Only while no contact holds a value.0–100 items |
| placeholder | string or null | New hint shown in the empty input; null clears it.0–255 characters |
| read_only | boolean | true = your team cannot edit the value in the app; the API still can. |
| visibility | string | manager_only hides the field from agents.one of: visible_to_all, manager_only |
Headers
| Field | Type | What it is |
|---|---|---|
| Authorizationrequired | header | Bearer $API_KEY — your API key. |
| Api-Version | header | The API version to use, e.g. 2026-10-01. Default: the version your key is pinned to.one of: 2026-10-01 · pattern ^\d{4}-\d{2}-\d{2}$ |
| Idempotency-Key | header | Any unique string (8–128 characters). A retry with the same key returns the first answer instead of running twice. Kept 24 hours.pattern ^[A-Za-z0-9._:-]+$ · 8–128 characters |
Response 200
The field after the change, and which properties changed (`[]` = nothing to do).
| Field | Type | What it is |
|---|---|---|
| fieldrequired | object | |
| idrequired | string | |
| keyrequired | string | Use this key in attributes: { "<key>": value }. |
| labelrequired | string or null | |
| typerequired | string or null | |
| optionsrequired | array of object or null | Allowed values for select / multiselect / radio fields. |
| labelrequired | string | |
| valuerequired | string | |
| read_onlyrequired | boolean | |
| visibilityrequired | string or null | visible_to_all or manager_only. |
| in_userequired | boolean or null | |
| created_atrequired | string or null | |
| placeholderrequired | string or null | Hint shown in the empty input (none for checkbox, boolean and choice fields). |
| updated_atrequired | string or null | |
| changedrequired | array of string | The properties this call changed; [] = it already was like this (nothing written). |
| dry_run | boolean | true when this was a dry run: every check ran and nothing changed. |
Errors
Errors are application/problem+json. Branch on code.
| Status | Code | When |
|---|---|---|
| 400 | invalid_input | A field is missing or has the wrong format. |
| 401 | unauthenticated | The Authorization header is missing, the key is unknown, expired or revoked. |
| 403 | entitlement_required | The workspace's plan does not include API access ( |
| 403 | forbidden | Your key's staff member is not a manager. |
| 403 | insufficient_scope | The key does not have the permission this operation needs. |
| 404 | not_found | No custom field with this id in your workspace (or it is visible to managers only and your key's staff member is not one). |
| 409 | conflict | Contacts hold values: the type cannot change ( |
| 429 | rate_limited | The key or workspace went over its rate limit. Wait for |
| 503 | upstream_unavailable | A service behind the API is briefly unavailable. Safe to retry with backoff. |
| 504 | timeout | The change did not finish in time. Retry with the same Idempotency-Key: it never runs twice. |
Examples
Rename a field and add a choice
Request body
{
"label": "Loyalty level",
"add_options": [
"platinum"
],
"rename_options": [
{
"value": "gold",
"label": "Gold ✨"
}
]
}Response 200
{
"field": {
"id": "32",
"key": "loyalty_tier",
"label": "Loyalty level",
"type": "select",
"options": [
{
"label": "Gold ✨",
"value": "gold"
},
{
"label": "Silver",
"value": "silver"
},
{
"label": "platinum",
"value": "platinum"
}
],
"read_only": false,
"visibility": "visible_to_all",
"in_use": true,
"created_at": "2026-09-02T08:00:00Z",
"placeholder": null,
"updated_at": "2026-09-29T09:00:00Z"
},
"changed": [
"label",
"options"
]
}Operation path
The same operation is also at POST /v1/ops/crm_update_custom_field, with every field in the JSON body.