--- name: ghl-custom-fields-api description: Endpoints reales de GHL para gestionar custom fields de contact/opportunity. V2 no sirve. Matriz de mutabilidad del PUT y asimetrías read/write. metadata: type: reference --- Validado empíricamente el 2026-05-23 contra la cuenta DEMO (`Vf7qQl3L9vakJ8hDtQ8e`). ## Endpoints relevantes **Para contact y opportunity custom fields, usar SIEMPRE V1**: `/locations/{locId}/customFields/*` con `Version: 2021-07-28`. **Custom Fields V2** (`/custom-fields/*`) NO funciona para contact/opportunity — GHL responde explícitamente: *"Fields with model contact is not supported on this route"*. V2 es solo para custom objects definidos por el usuario. **NO existe endpoint upsert** para contact/opportunity. Hay que hacer GET-or-create del lado del cliente. ## CRUD V1 disponible - `GET /locations/{locId}/customFields` → lista todos - `GET /locations/{locId}/customFields/{cfId}` → uno solo, response `{customField: {...}, traceId}` envuelto - `POST /locations/{locId}/customFields` → crea - `PUT /locations/{locId}/customFields/{cfId}` → actualiza - `DELETE /locations/{locId}/customFields/{cfId}` → borra, response `{succeded: true}` (GHL escribe `succeded` con un solo "e") ## Matriz de mutabilidad del PUT | Propiedad | Comportamiento | |---|---| | `name`, `placeholder`, `position`, `description`, `parentId`, `options` | ✅ Acepta y aplica | | `fieldKey` | ❌ 422 "property fieldKey should not exist" — inmutable absoluto | | `dataType` | ⚠ 200 OK pero **silently ignored** — inmutable de facto. La API miente. | | `picklistOptions` | ❌ 422 "property picklistOptions should not exist" — usar `options` en writes | **Why importante:** el caso `dataType` es trampa real. Si tu repair script asume que 200 = aplicado, te equivocas. Siempre verificar con GET post-PUT cuando hay duda. ## Asimetrías read vs write | Concepto | Key al leer | Key al escribir | |---|---|---| | Opciones de SINGLE_OPTIONS | `picklistOptions` | `options` | | Valor de CF en contact | `value` | `value` | | Valor de CF en opportunity | `fieldValue` | `value` | ## dataTypes válidos `TEXT, LARGE_TEXT, NUMERICAL, PHONE, MONETORY, CHECKBOX, SINGLE_OPTIONS, MULTIPLE_OPTIONS, FLOAT, TIME, DATE, TEXTBOX_LIST, FILE_UPLOAD, SIGNATURE, RADIO` Nota la ortografía: `NUMERICAL` (no NUMERIC), `MONETORY` (no MONETARY). GHL las escribe así. ## Implicación para reparación Lo único auto-reparable con seguridad: 1. Crear campo faltante (id y fieldKey serán nuevos, no copiados). 2. Sincronizar opciones (agregar las que faltan). 3. Renombrar (riesgo medio — puede romper workflows del cliente). Lo que NO se debe auto-reparar nunca: 1. Cambiar dataType (imposible vía API; requiere delete + recreate = data loss). 2. Borrar campos sobrantes (data loss). 3. Mergear duplicados (decisión manual de cuál preservar). Ver también [[brand-bucefalo-e3]].