> ## Documentation Index
> Fetch the complete documentation index at: https://agents.nanonets.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# GoHighLevel Create Note

> This document covers `gohighlevel_create_note` and the `custom_fields` support added to the GoHighLevel contact and opportunity write tools.

This document covers `gohighlevel_create_note` and the `custom_fields` support added to the GoHighLevel contact and opportunity write tools.

## Authentication

GoHighLevel tools use the existing GoHighLevel connector. Both values are resolved from the integration row and injected from the connector:

* `private_token` — GoHighLevel Private Integration Token
* `location_id` — sub-account location id (only needed by tools whose endpoint requires it)

## `gohighlevel_create_note`

Adds a timeline note to a GoHighLevel contact via `POST /contacts/{contactId}/notes`. This is a **mutating** tool.

### Notes attach to contacts, and link to opportunities

GoHighLevel scopes notes to the contact — there is no opportunity-scoped note endpoint, so you always pass a **contact id**.

To make a note attributable to one opportunity, also pass `opportunity_id`. The tool sends it as a `relations` entry (`objectKey: "opportunity"`). Without it, a contact holding two opportunities shows the same undifferentiated note on both — so supply it whenever the note is about a particular opportunity.

`relations` is absent from GoHighLevel's published v2 note schema but is the real mechanism: notes read back from a live location carry `relations: [{"objectKey": "opportunity", "recordId": "..."}]` (verified 2026-08-03). The tool reports the link **GoHighLevel actually recorded**, not the one requested — `opportunity_linked` is `false` and the summary says "not confirmed" if the relation is missing from the response, so a future API change surfaces instead of passing as a clean success.

### Inputs

Required:

* `contact_id`: Id of the contact to annotate. Look it up with `gohighlevel_search_contacts`, or take it from an opportunity.
* `body`: Note text. Plain text; newlines are preserved. Whitespace-only bodies are rejected rather than creating an empty timeline entry.

Optional:

* `user_id`: GoHighLevel user id to attribute the note to.
* `opportunity_id`: Opportunity to link the note to. Omitted entirely from the request when absent, rather than sent as an empty array.

### Output

Structured content includes `note_id` and the full `note` object. When `opportunity_id` was supplied it also carries `opportunity_linked` (bool) and `linked_opportunity_id`, read back from the created note's `relations`. The markdown summary truncates the body at 120 characters (counted in runes); the full text stays in the structured payload.

### Notes

* There is **no idempotency**. Calling twice creates two notes. Prefer one note per distinct event.

## `custom_fields` on contact and opportunity writes

`gohighlevel_create_contact`, `gohighlevel_update_contact`, `gohighlevel_create_opportunity` and `gohighlevel_update_opportunity` all accept an optional `custom_fields` array:

```json theme={null}
{
  "custom_fields": [
    { "key": "contact.office_key", "value": "151547" },
    { "id": "aBcD1234", "value": "Zoc Oregon" }
  ]
}
```

Each entry needs **either** `id` **or** `key`, plus a `value` passed **as a string** (a numeric or boolean field takes its value in string form, e.g. `"42"` or `"true"` — the input schema declares `value` as `string`). Fields you omit are not included in the write; whether GoHighLevel merges or replaces the resource's other custom fields on update has **not been verified live** — confirm before relying on a partial `custom_fields` update to preserve values you didn't send.

An entry with neither `id` nor `key`, or with no `value`, is rejected with an error naming the offending index — a silently skipped field would look like a successful write that never landed. Validation runs **before** any HTTP call, so malformed input doesn't cost a round trip or a partial write.

`gohighlevel_create_contact` and `gohighlevel_update_contact` also expose the address fields the API already supported: `address1`, `city`, `state`, `postal_code`, `country`.

The `field_value` wire key is **verified against a live location** (2026-08-03): a contact written with `customFields: [{id, field_value}]` echoed back `fieldValue` and read back `value` carrying the written value. GoHighLevel is asymmetric here — it accepts `field_value` on write and returns `value` on read. Note that opportunities read back a different, typed shape (`fieldValueString` / `fieldValueNumber` / `fieldValueDate` / `fieldValueArray`), and the test location defines no opportunity-scoped custom fields, so **`custom_fields` on the opportunity tools is still unverified** — confirm before relying on it there.
