Skip to content
API PlatformDevelopers
PATCH/v1/custom-fields/{custom_field_id}beta

Update 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 key is 400 (reason: key_immutable); create a new field instead.
  • Options: add_options appends choices (strings, or {label, value}; values unique), rename_options gives existing options a new label by value (the value, and every stored answer, stays), remove_options takes options out. A choice field keeps at least one option, and at most 500.
  • While contacts hold values (in_use: true) the type cannot 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 needs add_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

Path

The custom field id.

Body

Optional: the field's current key, to make sure you change the right field. It cannot change.

New label (any language).

New type. Only while no contact holds a value.

Choices to add: strings (label = value) or `{label, value}`.

`{value, label}`: a new label for the option with this value.

Option values to remove. Only while no contact holds a value.

New hint shown in the empty input; `null` clears it.

`true` = your team cannot edit the value in the app; the API still can.

`manager_only` hides the field from agents.

Protects against doing it twice if you retry: a retry with the same key gets the first answer back instead of running again.

Test mode (dry run): nothing will change
Turns test mode off for this page only. It switches back when you leave the page.

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

Parameters
FieldTypeWhat it is
custom_field_idrequiredstring | integer · pathThe custom field id.pattern ^[1-9]\d{0,18}$

Body

Body fields
FieldTypeWhat it is
keystringOptional: the field's current key, to make sure you change the right field. It cannot change.1–64 characters
labelstringNew label (any language).1–255 characters
typestringNew 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_optionsarray of string | objectChoices to add: strings (label = value) or {label, value}.0–100 items
rename_optionsarray of object{value, label}: a new label for the option with this value.0–100 items
valuerequiredstring1–255 characters
labelrequiredstring1–255 characters
remove_optionsarray of stringOption values to remove. Only while no contact holds a value.0–100 items
placeholderstring or nullNew hint shown in the empty input; null clears it.0–255 characters
read_onlybooleantrue = your team cannot edit the value in the app; the API still can.
visibilitystringmanager_only hides the field from agents.one of: visible_to_all, manager_only

Headers

Headers
FieldTypeWhat it is
AuthorizationrequiredheaderBearer $API_KEY — your API key.
Api-VersionheaderThe 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-KeyheaderAny 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).

Response fields
FieldTypeWhat it is
fieldrequiredobject
idrequiredstring
keyrequiredstringUse this key in attributes: { "<key>": value }.
labelrequiredstring or null
typerequiredstring or null
optionsrequiredarray of object or nullAllowed values for select / multiselect / radio fields.
labelrequiredstring
valuerequiredstring
read_onlyrequiredboolean
visibilityrequiredstring or nullvisible_to_all or manager_only.
in_userequiredboolean or null
created_atrequiredstring or null
placeholderrequiredstring or nullHint shown in the empty input (none for checkbox, boolean and choice fields).
updated_atrequiredstring or null
changedrequiredarray of stringThe properties this call changed; [] = it already was like this (nothing written).
dry_runbooleantrue when this was a dry run: every check ran and nothing changed.

Errors

Errors are application/problem+json. Branch on code.

StatusCodeWhen
400invalid_input

A field is missing or has the wrong format. errors[] points at each field. Nothing to change; a different key (reason: key_immutable); options on a field that is not a choice field; an unknown or repeated option value; a placeholder on a type that has none.

401unauthenticated

The Authorization header is missing, the key is unknown, expired or revoked.

403entitlement_required

The workspace's plan does not include API access (api_access).

403forbidden

Your key's staff member is not a manager.

403insufficient_scope

The key does not have the permission this operation needs.

404not_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).

409conflict

Contacts hold values: the type cannot change (reason: type_change_in_use) and options cannot be removed (reason: option_in_use); or another field has this label (reason: duplicate_label); or the field changed elsewhere while the request ran (reason: changed_concurrently: read it again, then retry). Also returned while a request with the same Idempotency-Key is still running.

429rate_limited

The key or workspace went over its rate limit. Wait for Retry-After seconds.

503upstream_unavailable

A service behind the API is briefly unavailable. Safe to retry with backoff.

504timeout

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.