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

# Contacts API

> Read, search, create, update, and enrich contacts through the Goliath developer API

## About

Everything the CRM does with contacts is available programmatically: look up
and search contacts, create and update them, add notes and tags, assign
teammates, and skip-trace (enrich) them for phone numbers and emails.

All operations go through the developer API gateway — one endpoint, called by
`operationId`. If you haven't set up a key yet, start with the
[Developer API Overview](/developer-api/overview).

## Operations

### Reading contacts (`READ` scope)

| operationId             | What it does                                                                                                                                                                                                                               |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `getContact`            | Fetch one contact by id — identity, channels, addresses, properties, tags/lists, custom-field values, teammates, notes, and linked deals. Custom fields include the org's definitions even when this contact's `values` are null.          |
| `findContacts`          | Fuzzy-search contacts by name, phone, email, or address. Results are relevance-ranked candidates, not guaranteed exact matches; verify the returned name before using its `contactId` in a write.                                          |
| `findContactsByNote`    | Fuzzy-search note bodies from the last year and return one best snippet per matching contact. Searches at most 500 candidate notes per archive state and returns at most 50 contacts, so `totalCount` is not an exhaustive org-wide count. |
| `listContactActivities` | Page one contact's calls, texts, emails, notes, and system events. Email entries include full plain-text `textExtract` when available; calls include disposition and disposition note. Filter by activity type and direction.              |
| `countContacts`         | The number of active contacts visible to your key, in one call — the same total the Contacts page shows.                                                                                                                                   |

### Writing contacts (`WRITE` scope)

| operationId              | What it does                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `createContact`          | Create a contact. Requires `sellerFirstName`, `sellerLastName`, and `type` (`LISTING` \| `INVESTMENT` \| `BUYER` \| `UNDETERMINED`); phones and emails optional.                                                                                                                                                                                                                                                                                                       |
| `updateContact`          | Update one contact by `contactId` — identity, archive state, channels, addresses, properties, relatives, custom-field values, files, tags/lists, teammates, and teams. Add/remove fields are deltas, not replacements; the response echoes custom-field values for read-back.                                                                                                                                                                                          |
| `addContactNote`         | Add a text note to a contact's timeline. The response is the owning contact, not the note; report its echoed name and re-read the contact if you need the new note id.                                                                                                                                                                                                                                                                                                 |
| `editContactNote`        | Rewrite an existing note's body by `noteId`. The note keeps its id and timeline position. Pass the **full** replacement body — this replaces the text, it doesn't append. `noteId`s come from `getContact` (`notes[]`) or a `NOTE` entry in `listContactActivities`; `addContactNote` doesn't return one, so re-read the contact to find the note you just wrote.                                                                                                      |
| `deleteContactNote`      | Permanently delete a note by `noteId`. No undo — when only the wording is wrong, use `editContactNote` instead. One note per call. Returns the owning contact with its remaining notes.                                                                                                                                                                                                                                                                                |
| `mergeContacts`          | Merge duplicates into one surviving record. `contactIds` is the full set (2 or more); `primaryContactId` is the survivor and must be one of them — it keeps its own custom fields and agent while absorbing the others' notes, tags, activities, and campaign slots, and the losers are archived. `name` is the survivor's resulting display name; optional `pointPersonUserId` decides which agent survives when the merged contacts disagree. Applied synchronously. |
| `addContactTags`         | Apply existing tags (by tag id, from `searchContactTags`/`createContactTag`) to contacts. Async — returns a `bulkTaskId` to poll with `getBulkTask`.                                                                                                                                                                                                                                                                                                                   |
| `removeContactTags`      | Remove free-form tags from contacts without deleting the tag definition. Async; tag-removal workflows may run. Use ids from `searchContactTags.tags` — list/custom-field ids do nothing.                                                                                                                                                                                                                                                                               |
| `addContactsToList`      | Add contacts to one or more lists. Async; returns a `bulkTaskId`. List membership changes can trigger contact workflows.                                                                                                                                                                                                                                                                                                                                               |
| `removeContactsFromList` | Remove contacts from lists without deleting the lists. Async and safe when a contact is already absent.                                                                                                                                                                                                                                                                                                                                                                |
| `assignContact`          | Assign a teammate to contacts (default role `POINT_PERSON`; `PARTICIPANT` also allowed). Get `userId` from `listTeammates`. Async — returns a `bulkTaskId`.                                                                                                                                                                                                                                                                                                            |
| `unassignContact`        | Remove one or more teammates from contacts. Async; it does not hand work to a replacement, except that one remaining participant is promoted when they are the only assignee left. Re-read ownership afterward.                                                                                                                                                                                                                                                        |
| `archiveContacts`        | Archive (soft-delete) contacts by id. Runs as an async bulk job — returns a `bulkTaskId` to poll with `getBulkTask`. Recoverable.                                                                                                                                                                                                                                                                                                                                      |
| `unarchiveContacts`      | Restore archived contacts by explicit id. Async. Restoring a duplicate archived by `mergeContacts` does not restore data moved to the survivor.                                                                                                                                                                                                                                                                                                                        |
| `renameContactTag`       | Rename a free-form tag in place while preserving memberships, filters, and workflow references.                                                                                                                                                                                                                                                                                                                                                                        |
| `renameContactList`      | Rename a list in place while preserving memberships, filters, and workflow references.                                                                                                                                                                                                                                                                                                                                                                                 |
| `setContactsCustomField` | Set or clear one `DROPDOWN` custom field across many contacts. Values are option labels, not ids. Async; poll `getBulkTask` because validation failures surface on the worker.                                                                                                                                                                                                                                                                                         |
| `deleteContactTags`      | Delete free-form tags by id (from `createContactTag`); also removes them from any contacts.                                                                                                                                                                                                                                                                                                                                                                            |
| `deleteContactLists`     | Delete contact lists by id (from `createContactList`); also drops membership.                                                                                                                                                                                                                                                                                                                                                                                          |

<Warning>
  **Bulk contact writes can start workflows**

  Adding or removing tags/lists and setting a custom field publish the same
  contact-change events as the app. A matching CONTACT workflow can therefore
  start and send. Check the org's workflows before changing a large selection,
  and poll every returned `bulkTaskId` before reporting success.
</Warning>

<Note>
  **Who may edit or delete a note**

  A non-admin key may only edit or delete notes the key owner **authored**;
  anyone else's are refused. A note whose `author.kind` is `AI` counts as
  someone else's, so a non-admin key is refused on one — check the author before
  offering to correct a note. A team-admin key can edit or delete any note.
</Note>

### Custom fields (`READ` + `WRITE` scopes)

Custom fields are organization-wide **definitions**. These operations manage the
definitions themselves; you write per-contact *values* through the
`addCustomFieldValues` input on `createContact` and `updateCustomFieldValues` on
`updateContact`, using the `customFieldId` from here.

| operationId                  | Scope   | What it does                                                                                                                                                                                                                                                                                                                                                                                          |
| ---------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `listContactCustomFields`    | `READ`  | List the org's contact custom-field definitions — id, name, type (`TEXT` \| `NUMBER` \| `DOLLAR` \| `DATE` \| `LINK` \| `DROPDOWN`), dropdown options, `allowMultiple`, `defaultValue`.                                                                                                                                                                                                               |
| `createContactCustomField`   | `WRITE` | Create a definition: `name` + `type`; `options` required for `DROPDOWN`, `allowMultiple`/`defaultValue` optional. Applied synchronously. Returns the org catalog — read the new id back by matching the name.                                                                                                                                                                                         |
| `updateContactCustomField`   | `WRITE` | Edit a definition in place by id — the right way to fix a wrong name, description, options, or default rather than creating a second field. Everything except the id is optional and omitted fields are untouched. The **type cannot be changed**. `options` applies to `DROPDOWN` only and **replaces** the whole list, so send every option you want to keep, each existing one as `{ id, label }`. |
| `reorderContactCustomFields` | `WRITE` | Set the display order. Presentation only — no definition, option, or recorded value is touched. Pass **every** custom-field id the org has, in the order you want: call `listContactCustomFields` immediately before and send the complete reordered list. A partial list, an unknown id, or a repeated id is rejected.                                                                               |
| `deleteContactCustomField`   | `WRITE` | Permanently delete a definition. **Destructive and irreversible** — every value recorded on every contact for this field is deleted with it and is not moved anywhere. Prefer `updateContactCustomField` when the field is merely wrong. Requires a `dependencyResolution.impactVersion` from `getDeletionImpact` (`targetKind: CONTACT_CUSTOM_FIELD`).                                               |

<Warning>
  **Check the blast radius before deleting a field**

  Call `getDeletionImpact` with `targetKind: CONTACT_CUSTOM_FIELD` and the field
  id first. It reports how many contacts hold a value and which filters and
  workflows reference the field, and returns the `impactVersion` you must hand
  back as `dependencyResolution.impactVersion` — pass an empty `replacements`
  array when it reports no replacement requirements.
</Warning>

### Enrichment (`ADMIN` scope)

| operationId                 | What it does                                                                                                |
| --------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `enrichContact`             | Skip-trace a contact to discover phone numbers and emails. **Consumes one skip-trace credit (real money).** |
| `createContactFromProperty` | Create a CRM contact from a property. **Triggers skip-trace enrichment — consumes one skip-trace credit.**  |

<Warning>
  **Credit-spending operations**

  Enrichment operations spend your organization's skip-trace credits, so they
  require the `ADMIN` scope and the key owner must currently be a team admin.
  Check balances first with `getBillingSummary` — see the
  [Billing API](/developer-api/billing).
</Warning>

<Note>
  **Organization guard**

  Operations that take a `contactId` are org-guarded on it: a contact outside
  your organization returns `403 forbidden` before the operation runs. Other ids
  you pass — a tag or teammate — aren't pre-checked at the gateway; the operation
  validates them as it executes and surfaces its own error (e.g. `NOT_FOUND` for
  an unknown tag). See
  [Authorization](/developer-api/authorization) for the model.
</Note>

## Example

Find a contact, then add a note to it:

```bash theme={null}
curl -X POST https://server.goliathdata.com/api/v1/call \
  -H "Authorization: Bearer gsk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "operationId": "findContacts", "variables": { "searchTerm": "maria alvarez" } }'

curl -X POST https://server.goliathdata.com/api/v1/call \
  -H "Authorization: Bearer gsk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "operationId": "addContactNote", "variables": { "contactId": "CONTACT_ID", "body": "Voicemail left 7/9 — call back Friday." } }'
```

Full variable schemas, response shapes, and worked examples for every
operation are available from the
[discovery endpoint](/developer-api/overview#discovering-operations).

<Note>
  Contacts also flow *out* of Goliath — workflow webhook steps POST a contact
  payload to your URLs. See the
  [Contact Webhook Payload](/contact-webhook) reference.
</Note>
