Skip to main content
A sub-account can keep its contacts in GoHighLevel, or keep them here. When it keeps them here there is nowhere else they exist, so this is the system of record: what you write is what the agent dials and what the prompt reads. Nothing syncs it back from somewhere else, and nothing overwrites it overnight. Everything on this page is the Native Contacts API. The ordinary Contacts API still works on these sub-accounts too, and is the one to use when you want calling history across every sub-account at once, whatever CRM each of them uses.
Which one a sub-account is comes from its crm field, in GET /api/v2/projects/{id}. native means it keeps contacts here. Every endpoint on this page answers 400 for a sub-account with a CRM connected.

What a contact holds

Deliberately the minimum a call needs, plus whatever you define on top.

Getting contacts in

Four doors, one import. They all end up at the same place, so a number that arrives twice updates one contact rather than making two.

One at a time, from code

POST /api/contacts/add/{locationId} with an API key. See Add a contact.

From a form, Zapier or Make

The sub-account’s lead link, below. No header to set.

A file

Import contacts from CSV, or the same button on the Contacts page.

By hand

Create a native contact, or Add contact on the Contacts page.

Phone numbers are normalised, not guessed

A ten-digit number is read as North American. Anything else must carry + or 00. A number that cannot be made into E.164 is refused, with the reason, rather than being stored as somebody else’s number:
That is the one validation worth designing your import around. In a CSV it costs you a named line in skipped; from the API it costs you the request. GET /api/native-contacts/lead-webhook returns a token. Post the same body you would send to Add a contact to:
It reads the lead exactly as the other doors do, triggers included.
The token in that link is its only credential. Anyone holding it can add contacts to the sub-account. Keep it out of client-side code and anywhere a log would carry it, send it over HTTPS only, and use an API key instead wherever your sender can set a header. If it leaks, rotate it: the old link stops working at once, with no grace period, so change your form or Zap in the same sitting.

Finding them again

List native contacts pages newest first. search matches a first name, a last name, an email or any part of the number, so a caller id in any spelling finds the row. outcome narrows to one of two: transferred for the people a call handed to somebody, who are still callable, and not_qualified for the people who asked not to be called, by either door. total counts the whole filtered set rather than the page you were given.

Custom fields

A sub-account defines its own fields, and their values live on each contact. The key is derived from the label once and never changes, so renaming “Budget” to “Monthly budget” cannot break a prompt that reads {{budget}}. Types are text, number, date and select, and a value is checked against the type: a date must be YYYY-MM-DD, a select must be one of its options, case-insensitively.
Set values with customFields on a create or an update. An empty string clears one, which is also the only way to let a “field is set” trigger fire again.
A label whose key would collide with a standard call variable, such as name, phone, timezone or caller_pref, is refused. Custom fields are applied to a call’s variables after the engine’s own, so such a field would silently replace the lead’s real name, or their memory, in the prompt.

Do not call

POST /api/native-contacts/{contactId}/do-not-call with doNotCall: true stops every campaign, cadence and test dialling that contact. Send false to clear it. The reason you send is kept, and an opt-out the contact made on a call keeps its own words rather than taking yours. The decision outlives the contact. Delete a suppressed contact and the number keeps its marker. If that number is added again later, the new contact comes back already suppressed, carrying the original date, reason and source, and the response says so:
You did not ask for that, which is exactly why you are told. A lead you have paid for and that nothing will ever dial must not come back as a bare success. If you believe the suppression is wrong, clear it deliberately with the do-not-call endpoint.

Deleting a contact

Deleting is not just removing a row.
1

Ask what it would take with it

Preview the delete. It names the live bookings, says whether the contact is on a call right now, and returns the appointment ids.
2

Pass those ids back

Send them as expectedAppointmentIds. If the contact has been booked again since you looked, the delete is refused with 409 rather than quietly cancelling something nobody saw.
3

Bookings are cancelled first

A delete that cannot cancel one of them is refused whole, and the error names which. Removing the row without cancelling would leave the appointment live, the seat held and the attendee expecting it.
Deleting several at once is bulk delete. It is partial on purpose and answers with deleted, failed and remaining: one contact who is on a call must not keep the other forty-nine, and the run stops at a time budget rather than running past the point where you stopped waiting. Send remaining again to carry on.
A delete is not erasure. The contact’s do-not-call marker stays, because that is the only thing stopping a later import bringing the number back clear.

Starting a campaign for contacts you already have

A new contact starts any campaign whose trigger is “New contact”. For contacts that were already here when the campaign was made, use Put contacts into a campaign. It answers 202 with what was accepted and a count of what was skipped, by reason. A contact on do-not-call is always skipped, and nothing you send overrides that.

Contact memory

caller_pref is one block of text the next call reads. The post-call step writes what the agent learned; you can add what only a person knows:
Notes are the other half of that pair and the distinction matters: a note is for people and appears on the timeline; memory is read by the agent on the next call.

Where to go next

Native Contacts API

Every operation, with schemas and examples.

Calendars

Booking without a CRM: the diary these contacts are booked into.