Tags and custom fields
Tags and custom fields are how you sort and label your contacts. A tag is a short label, like vip. A custom field holds one value per contact, like a renewal date, and you can use it in a message as {{custom.<name>}}. To save a filter or read the values to filter by, see Saved views and filter helpers.
A team member limited to their assigned contacts only sees and changes their own and unassigned contacts on these routes.
#Tags
Tags are exact text, up to 64 characters, and case-sensitive: VIP isn’t vip. The Opted Out tag is managed by Ready, so you can’t add or remove it here. To set the tags of one contact, update the contact.
#List tags
Every tag on your contacts, A to Z. Tags an automation starts on ("contact tagged") are listed too, even before any contact has them. The list is cached and can be up to 5 minutes behind. Needs the view_contacts permission.
Parameters
with_countsstring · query1ortruereturns{tag, count}objects instead of plain names.countis how many contacts have the tag; an automation’s tag no contact has yet counts0.
Returns a list of tag names (or {tag, count} objects) in data.
Errors
| HTTP | Code | When |
|---|---|---|
| 500 | no code | We couldn’t read your tags. Retry. |
#Tag or untag many contacts
Adds one tag to, or removes it from, a set of contacts. Pick the contacts one way: by id, by assignee, by filter, or all of them. Sending it twice is safe: contacts that already match aren’t changed again. Tag automations and Zaps run for the contacts that changed. Needs the view_contacts and edit_contacts permissions.
Parameters
tagstringRequired- Trimmed and cut to 64 characters.
actionstringDefaultaddaddorremove. Leave it out to add. Any other value is refused withinvalid_field.contact_idsinteger[]Max50,000- The contacts to change. Ids that aren’t valid are dropped.
assigned_tointeger- Every contact assigned to this team member.
filtersobject- Every contact that matches, using the filters of List contacts as keys, like
{"tag": "trial", "state": "TX"}. Spam contacts are left out unless you send"include_spam": "1". A key that isn’t a filter is refused withunknown_filter, so a typo never matches every contact. allbooleantrue: every contact on your account. Use with care.
Send one of contact_ids, assigned_to, filters or all. If you send more than one, the first in that order wins.
Returns
action, tagstring- What was done, and the tag as saved.
updatedinteger- Contacts that changed.
undo_tokenstring | null- Reverses this change for 48 hours:
POST /contacts/bulk-actions/<undo_token>/undo.nullwhen nothing changed. Over 20000 contacts there is no undo: the answer is202withqueued: true,job_id,action,tagandundo_unavailable: "selection_too_large", and the change runs in the background. warning, warning_codestring- Only when we couldn’t check your automations and Zaps for this change. The tags are saved.
warning_codeiscontact_events_queued.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | tag_required | No tag. |
| 400 | no code | The tag is Opted Out, or filters can’t be read. |
| 400 | selection_required | No contacts picked. |
| 400 | no_valid_contacts | None of the contact_ids is a valid id. |
| 400 | too_many_contacts | More than 50,000 contact_ids. |
| 400 | invalid_field | assigned_to is too large to be an id, or a field has the wrong type. |
| 429 | rate_limited | Over about 120 bulk changes a minute per account. |
| 500 | bulk_tag_failed | The change failed. Some contacts may be tagged; list them before you retry. |
#Remove a tag from every contact
Takes one tag off every contact on your account that has that exact spelling. Sending it again changes nothing. undo_token puts the tag back for 48 hours. Needs the view_contacts and edit_contacts permissions.
Parameters
tagstring · pathRequired- The tag, URL-encoded:
Spring%20promo.
Returns
removedinteger- Contacts the tag was removed from.
tagstring- The tag, as we read it.
undo_tokenstring | null- Puts the tag back on those contacts for 48 hours:
POST /contacts/bulk-actions/<undo_token>/undo.nullwhen nothing changed or more than 20000 contacts changed. warning, warning_codestring- As in Tag or untag many contacts.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | tag_required | The tag is blank. |
| 400 | no code | The tag is Opted Out. |
| 500 | tag_delete_failed | Nothing was changed. Retry. |
#Custom fields
A custom field is defined once for your account, then each contact can hold a value for it. Names are unique on your account, ignoring case and spaces: Renewal Date and renewal date are the same field. Values are stored as text. You can also set values by name with custom_fields when you update a contact. A value set by field id is what texts and filters use; the contact’s custom_fields can still show the earlier value, so read values with Get a contact’s custom field values.
#The custom field object
idinteger- The field id.
user_idinteger- Your account id.
namestring- The field’s name. In a message it’s
{{custom.<name>}}, lowercase, with spaces as_. field_typestring- Lowercase:
text,number,dateordropdown. The type picks the input in the dashboard; values aren’t checked against it. optionsstring | null- A dropdown’s choices, one per line.
required, position, group_name, placeholderboolean, integer, string, string- Not set by this API.
created_atstring- When the field was made, ISO 8601.
#List custom fields
Every custom field on your account, oldest first. Fields from a CSV import or a connected GoHighLevel account are added to the list first. Any team member can call it.
Returns a list of custom field objects in data.
Errors
| HTTP | Code | When |
|---|---|---|
| 503 | no code | We couldn’t read the fields, or fields from an import are still being added. Retry in a few seconds. |
#Create a custom field
Adds a field. If you already have a field with that name, nothing changes and you get 200 with the list, so sending it twice is safe. Needs the edit_contacts permission.
Parameters
namestringRequired- Up to 128 characters. Can’t contain
{,}or|. field_typestringDefaulttext- Use
text,number,dateordropdown. Other words are saved as given, in lowercase, and show as a text box. optionsstring- For a dropdown: the choices, one per line. Up to 5000 characters.
Returns every custom field object on your account in data, the new one included.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | no code | No name, or the name has {, } or |. |
| 400 | invalid_field | A field isn’t text, or is too long. |
#Update a custom field
Renames a field or changes its type or options. Always send name, even when it stays the same; field_type and options you leave out keep their value. Values on contacts are kept. Needs the edit_contacts permission, and a team member limited to their assigned contacts can’t.
Parameters
idinteger · pathRequirednamestringRequired- Same rules as when you create one.
field_typestring- Leave it out to keep the field’s type. Use
text,number,dateordropdown. optionsstring- For a dropdown: the choices, one per line. Leave it out to keep the current choices.
Returns every custom field object on your account in data.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | no code | No name, or the name has {, } or |. |
| 400 | invalid_field | The id isn’t a whole number, or a field isn’t text or is too long. |
| 403 | no code | You only see your assigned contacts. |
| 404 | CUSTOM_FIELD_NOT_FOUND | No field with this id on your account. |
| 409 | no code | Another field already has this name. |
#Delete a custom field
Deletes the field and every value saved for it. Values that came from a CSV import stay in the contact’s custom_fields, and an import or sync won’t add the field back. Creating it again does. Needs the edit_contacts permission, and a team member limited to their assigned contacts can’t.
Returns { "success": true }. Sending it again returns 404.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_field | The id isn’t a whole number. |
| 400 | unknown_field | You sent a body. This route takes none. |
| 403 | no code | You only see your assigned contacts. |
| 404 | CUSTOM_FIELD_NOT_FOUND | No field with this id on your account. |
#Get a contact’s custom field values
The values saved on one contact. Fields with no value aren’t listed. A contact that isn’t on your account returns an empty list. Needs the view_contacts permission.
Parameters
contactIdinteger · pathRequired
Returns
A list in data, one item per value:
field_idinteger- The custom field’s id.
name, field_typestring- From the field.
valuestring- The value, as text.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_field | contactId isn’t a whole number. |
| 503 | no code | We couldn’t read the values. Retry. |
#Set a contact’s custom field values
Saves values by field id. Each value replaces the one saved; fields you don’t send are left alone. Sending it twice is safe. Needs the view_contacts and edit_contacts permissions.
Parameters
contactIdinteger · pathRequiredfieldsobject[]RequiredMax500- Each is
{"field_id": 31, "value": "2027-01-15"}. A value is text, a number ortrue/false, up to 2000 characters, and is saved as text.nullsaves an empty value.
Returns
skipped_field_idsinteger[]- Only when some ids aren’t fields on your account. Those weren’t saved; the rest were.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_field | contactId isn’t a whole number, fields isn’t a list, an item isn’t an object, or a value is an object or list. |
| 400 | no code | A value is over 2000 characters, or more than 500 items. |
| 404 | CONTACT_NOT_FOUND | No contact with this id on your account. |