π Describe the Bug
There is no API path to set or correct gender on a contact that already exists.
The two endpoints that could do it fail in different ways, and one of them fails
silently.
PUT /contacts/{contactId} rejects it β honestly.
PUT /contacts/{contactId} { "gender": "male" }
-> 422 { "message": ["property gender should not exist"],
"error": "Unprocessable Entity", "statusCode": 422 }
This matches the spec: UpdateContactDto does not declare gender. No
complaint about this part β the error is clear and correct.
POST /contacts/upsert accepts it and throws it away.
UpsertContactDto does declare gender. On an existing contact the call
returns 200, reports "new": false, and echoes back the stored value
rather than the submitted one. The submitted value is discarded with no error,
no warning, and no indication anything was ignored.
Verified two ways:
So gender is effectively write-once, at creation only. Once a contact
exists it can never be set, corrected, or cleared through the API.
For contrast, UpdateContactDto omits three fields relative to
CreateContactDto: locationId, gender, and companyName. locationId has
an obvious reason to be immutable β it is the contact's owning location, and
changing it would be a move rather than an edit. gender is a plain scalar
attribute with no equivalent rationale, and nothing in the documentation says it
is create-only.
π API Endpoint
POST /contacts/upsert, PUT /contacts/{contactId}
β
Expected Behavior
Any one of these would resolve it, in descending order of preference:
- Honour
gender on POST /contacts/upsert when new: false. It is
already declared in UpsertContactDto, so this is the behaviour the schema
already promises.
- Add
gender to UpdateContactDto so PUT /contacts/{contactId} can set
it, consistent with every other scalar contact attribute.
- At minimum: document that
gender is create-only, and have upsert
reject it on an existing contact the way PUT does, instead of returning
200 and discarding it. A documented field that is silently ignored is worse
than one that errors.
π» Screenshots or Code Samples
Minimal reproduction β no setup beyond a sub-account:
Product Area
contacts
π Use Case
We sync contact records from external systems into GoHighLevel sub-accounts on a
schedule. A sync resolves the existing contact, diffs it against the incoming
record, and writes what changed.
Because gender can only be written at creation:
- Contacts we adopt rather than create β ones that already existed in the
sub-account β can never have gender populated at all.
- If a source system corrects a contact's gender, we cannot propagate the
correction.
- We spent time building an upsert-based workaround for exactly this, because
the 200 response gave every indication it had worked. It had not.
We are not able to use POST /contacts/upsert as a general substitute for
update in any case: per #37 it resolves which contact to write from the
sub-account's duplicate-contact settings, so when an email matches one contact
and a phone matches another it updates whichever ranks first. When we have
already resolved a contact by id, we need an endpoint that writes to that id.
π¨ Why Should This Be Prioritized?
The silent-discard is the core of it. A 200 with the field echoed back at its
old value is indistinguishable from success unless you re-read and compare β
which is not something most integrations do after a write that reported OK.
Any integration that believes it is keeping gender in sync is silently wrong,
and will stay wrong indefinitely. If the field genuinely cannot be updated, a
422 β which PUT already returns for the same field β would surface that
immediately instead.
π§ Additional Context
π Describe the Bug
There is no API path to set or correct
genderon a contact that already exists.The two endpoints that could do it fail in different ways, and one of them fails
silently.
PUT /contacts/{contactId}rejects it β honestly.This matches the spec:
UpdateContactDtodoes not declaregender. Nocomplaint about this part β the error is clear and correct.
POST /contacts/upsertaccepts it and throws it away.UpsertContactDtodoes declaregender. On an existing contact the callreturns
200, reports"new": false, and echoes back the stored valuerather than the submitted one. The submitted value is discarded with no error,
no warning, and no indication anything was ignored.
Verified two ways:
So
genderis effectively write-once, at creation only. Once a contactexists it can never be set, corrected, or cleared through the API.
For contrast,
UpdateContactDtoomits three fields relative toCreateContactDto:locationId,gender, andcompanyName.locationIdhasan obvious reason to be immutable β it is the contact's owning location, and
changing it would be a move rather than an edit.
genderis a plain scalarattribute with no equivalent rationale, and nothing in the documentation says it
is create-only.
π API Endpoint
POST /contacts/upsert,PUT /contacts/{contactId}β Expected Behavior
Any one of these would resolve it, in descending order of preference:
genderonPOST /contacts/upsertwhennew: false. It isalready declared in
UpsertContactDto, so this is the behaviour the schemaalready promises.
gendertoUpdateContactDtosoPUT /contacts/{contactId}can setit, consistent with every other scalar contact attribute.
genderis create-only, and have upsertreject it on an existing contact the way
PUTdoes, instead of returning200and discarding it. A documented field that is silently ignored is worsethan one that errors.
π» Screenshots or Code Samples
Minimal reproduction β no setup beyond a sub-account:
Product Area
contacts
π Use Case
We sync contact records from external systems into GoHighLevel sub-accounts on a
schedule. A sync resolves the existing contact, diffs it against the incoming
record, and writes what changed.
Because
gendercan only be written at creation:sub-account β can never have
genderpopulated at all.correction.
the
200response gave every indication it had worked. It had not.We are not able to use
POST /contacts/upsertas a general substitute forupdate in any case: per #37 it resolves which contact to write from the
sub-account's duplicate-contact settings, so when an email matches one contact
and a phone matches another it updates whichever ranks first. When we have
already resolved a contact by id, we need an endpoint that writes to that id.
π¨ Why Should This Be Prioritized?
The silent-discard is the core of it. A
200with the field echoed back at itsold value is indistinguishable from success unless you re-read and compare β
which is not something most integrations do after a write that reported OK.
Any integration that believes it is keeping
genderin sync is silently wrong,and will stay wrong indefinitely. If the field genuinely cannot be updated, a
422β whichPUTalready returns for the same field β would surface thatimmediately instead.
π§ Additional Context
apps/contacts.jsonatmainin this repo:CreateContactDto(23 props) andUpsertContactDto(24 props) both declaregender;UpdateContactDto(20 props) does not.settings), [Bug Report: API Field Missing] Create / update contact date of birthΒ #124 (contact date-of-birth writability).