GoliathDevelopersGet an API key
Guides / Contacts API

Contacts API

Search, create, update and enrich contacts; manage links, Do Not Contact, AI pauses, sharing and suggestion cards through the Goliath developer API

About

Look up and search contacts, create and update them, add notes and tags, assign teammates, find duplicate groups, manage saved filters, and skip-trace (enrich) contacts for phone numbers and emails. You can also manage links and related people, Do Not Contact and AI pauses, direct mail, sharing with partner organizations, and the suggestion cards AI employees leave on contacts.

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.

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. A Do Not Contact record carries doNotContactSetAt and doNotContactSetBy (who set it).
getContactIdentity A contact’s own email addresses — never its related people’s, and never one marked WRONG. Use it to match an inbound email to the right contact.
getContactConnections A contact’s Links card (externalLinks), related people (linkedRelationships, with linkedContactId), relatives who are not contacts yet (subContacts), and any AI pause in force (aiPause). These are the ids the link and relationship writes take. A merged-away id resolves to the survivor.
getContactsProperties Many contacts’ linked properties in one read: contactIds in, each contact’s properties[] out with propertyString and property.id (the id a skip trace, export or bulk selection takes; null when the address never matched a parcel). Contacts the key owner may not open are left out.
findContacts Fuzzy-search contacts by name, phone, email, or address. Archived contacts are included by default, marked isArchived (pass includeArchived: false to drop them). 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.
findDuplicateContactGroups Find groups with the same name (ignoring case/spacing) sharing a phone or email, including chained matches. Reads only active contacts visible to the key owner. Page groups with limit (default 50, max 100) and offset; truncated means totalCount is a floor. Name variants and property-only matches are not covered, so an empty result is not proof there are no duplicates. matchBy: NAME groups by name alone (unverified), listing the phones and emails members share; tagIds limits to groups with a member carrying one of those tags or on one of those lists. Review each group before mergeContacts.
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.
listContactFilters List saved contact filters visible to the key owner: shared organization filters plus their own private ones. Each row includes the owner’s userId; check it before editing a teammate’s filter.
filterContacts Run a saved filterId or an unsaved filterTree, with limit/offset pagination and optional sort: { field, direction }. Returns contacts plus total and hasMore; an unsaved tree does not create a filter. Rows carry doNotContactReason and doNotContactSetAt.
searchContactTags Find existing contact tags and lists by name. Use the returned ids in contact writes.
contactTagUsage Free-form tags, least-used first, with contactCount (visible, unarchived), orgContactCount and dependents. maxContactCount: 0 lists unused tags; pass a filterTree to count only matching contacts per tag. Use it instead of counting contacts tag by tag.
countRecords Counts grouped by any field in one call — tasks per contact, teammate or status; contacts by source; deals by stage. spec is a page data spec without fields (aggregates only), fenced to what the key owner may see.
contactListUsage Every list, import upload list and export list with its member counts, emptiest first: visible unarchived contacts, plus organization-wide unarchived and archived counts and isEmpty (null unless the key owner can view all contacts). Narrow with kinds; onlyEmpty needs view-all access.
listSmsOptOuts Who opted out of texts (texted STOP, asked to stop, carrier opt-out, a teammate’s block), newest first, with since and limit (default 25, max 100). Each row has source, optedOutAt, reason, phoneNumber and the visible contacts carrying it. totalCount and notVisibleCount are null unless the key owner can see every contact. Undeliverable numbers (landlines, dead numbers) are not opt-outs and are left out.
duplicateContactsByRep findDuplicateContactGroups tallied per assigned rep (point person): how many duplicate groups hold each rep’s contacts. Unassigned contacts appear under a null rep. Optional matchBy as on findDuplicateContactGroups.
findContactDataProblems Contact data-shape problems in one call — emails with capitals, empty or one-word names, phones not in the stored 10-digit form. Per problem: the count of visible contacts and a sample with the bad value. Pass problem to list up to 500 of its contact ids past offset (the input lowercaseContactEmails takes).
findSharedPropertyAddresses Groups of two or more visible contacts linked to the same property address (by parcel, else mailing-address row, else normalized text), largest first, each member with how it is linked. Paged over groups with limit (default 50, max 100) and offset.
searchInteractions Find contacts by what was said on calls, notes, texts or emails — pass the clues as a sentence in query, optionally narrowed by kinds, since or contactId. Verify each clue in the top results’ history before naming anyone.

Use getFilter to read a saved criteria tree before editing it, and saveFilter(type: CONTACT) to create one. Filter authoring and vocabulary discovery are documented in the Properties API.

filterContacts and filtered countContacts calls check the resolved criteria against your organization’s plan. A field the plan excludes causes an operation error with code PLAN_ENTITLEMENT_REQUIRED, rather than an empty result. This applies to saved filters as well as inline trees; a saved filter does not preserve access to a feature your plan no longer includes.

For filterContacts.sort, direction is ASC or DESC and field is SELLER_INTENT_SCORE, LAST_COMMUNICATION, LAST_ACTIVITY, DIAL_ATTEMPTS (organization-wide outbound dials), MY_LAST_CALL, MY_LAST_TEXT, LAST_UPDATED, CREATED_AT, TASK_HIGHLIGHT, or AUCTION_DATE (earliest upcoming auction or sale day, with contacts having none last). Unknown fields are silently ignored and use the default order; a successful response does not prove a guessed sort field worked.

Writing contacts (WRITE scope)

operationId What it does
createContact Create a contact using input.sellerFirstName and input.sellerLastName. Optional addPhoneNumbers, addEmails, and address/custom-field inputs add details. type is optional and retained for compatibility; it does not set a deal type on the contact.
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. noteIds 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.
changeNotePinned Pin (isPinned: true) or unpin a note by noteId. A pin notifies the contact’s teammates.
createNoteReply Reply to a note as the key owner. Notifies the thread. Returns the replies; find yours by author and createdAt.
editNoteReply Replace a reply’s body by replyId (from getContact notes[].replies). Same authorship rule as editContactNote.
deleteNoteReply Delete a reply for good. Same authorship rule as editContactNote. Returns the replies left.
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, and activities, 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, and new ones by exact name in newTagNames (created as it runs). 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.
createContactTag Create an organization-wide free-form tag. Returns the tag catalog; read the new id back by name.
createContactList Create an organization-wide contact list. Returns the list catalog; read the new id back by name.
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.
setContactsTeams Put contacts on teams (addTeamIds, from listTeams) and take them off (removeTeamIds); replaceExisting: true makes addTeamIds the exact set. addTeamIds is required — pass [] to only remove. Async; returns a bulkTaskId.
lowercaseContactEmails Lowercase every capitalized email on up to 500 contacts in place, keeping each email’s id, ranking and verification. A contact already holding the lowercase twin keeps both. Get the ids from findContactDataProblems.
removeContactFiles Take files off a contact’s Files & Photos by file id (a Library filing’s uploadedFileId, or a files[].id from getContact), as the Files card’s delete does. Returns the files the contact still has.
controlContactsWorkflowRuns PAUSE or RESUME the workflow runs of explicit contacts; optional workflowGroupIds narrows it. RESUME only restarts runs someone paused by hand on a workflow version that is still active; other runs are skipped. Async; returns a bulkTaskId.
deleteContactFilter Permanently delete a saved CONTACT filter by filterId; no archive or undo. Property/deal filter ids are refused. Check the owner’s userId in listContactFilters first: an org admin can delete a teammate’s shared filter, while a teammate’s private filter is refused. Prefer updateFilter when the criteria only need correcting.

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, matchInboundTextsByValue and hideOnContactRecord.
getContactFieldVisibility READ Each contact field’s show-when rule; null when the field is always shown.
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. type retypes the field in place (same id), refused once a contact has a value, anything uses it, it has a default, or it is a dropdown. options applies to DROPDOWN only and replaces the whole list, so send every option you want to keep, each existing one as { id, label }. hideOnContactRecord takes a field (a system one such as Source too) off the contact record without touching its values. matchInboundTextsByValue (TEXT fields only) links a text from an unknown number that quotes the field’s value to the contact holding it; it matches only 6–64 character values with no spaces that mix letters and digits.
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.
addContactCustomFieldOption WRITE Add one option (label) to a contact DROPDOWN field without resending the whole list.
setContactCustomFieldVisibility WRITE Show the fields in customFieldIds only when a dropdown holds any of optionIds. Display only; values are untouched.
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).

Read the current links and relationships with getContactConnections.

operationId What it does
addContactExternalLink Add a titled url to a contact’s Links card.
updateContactExternalLink Change a contact link’s title or url; an omitted one is kept.
removeContactExternalLink Remove a contact link. Irreversible.
linkContactAsRelatedPerson Connect linkedContactId to parentContactId as a related person, with an optional free-text relation. A pair is unique in either direction: a repeat is refused as already linked. An archived or merged linkedContactId is refused.
updateContactRelationship Set a related person’s relation label and note. Both are written, so an omitted one is cleared — send the current note to keep it.
unlinkRelatedContact Disconnect a related person by relationshipId; both contacts stay. Irreversible.
convertSubContactToLinkedRelationship Promote a relative listed on a contact (subContactId) to a contact of its own, linked back. linkToExisting: true folds it into the organization’s existing contact for the same person.

Do Not Contact and AI pauses (WRITE scope)

operationId What it does
setContactDoNotContact Mark a contact Do Not Contact (optional reason): every text, email and call to it is blocked, its workflow runs stop for good, and its AI employees come off. Use the id getContact returns.
setPhoneDoNotContact Opt one phone number out of texts and calls for the whole organization, on every contact that carries it.
pauseContactAi Pause AI on one contact. mode: ALL — no AI employee runs, and workflow AI-employee steps that come due are skipped, not deferred. mode: MESSAGES — runs go on, but no AI text, email or Slack message is suggested or sent. A future pausedUntil ends it on its own; null lasts until resumed. This is not Do Not Contact: people can still reach the contact.
pauseContactsAi pauseContactAi for many contacts (same mode and pausedUntil). Async; returns a bulkTaskId.
resumeContactAi Turn AI back on for one contact so its AI employees can text and email it again. A contact with no pause is a no-op.
resumeContactsAi resumeContactAi for many contacts. Async; returns a bulkTaskId.

Direct mail (READ + WRITE scopes)

operationId Scope What it does
listContactDirectMail READ The letters sent to one contact by any mail provider, newest first, 20 a page (pass nextCursor as after): id, campaign label, status, prepared and mailed dates.
requestDirectMailCancellation WRITE Ask the mail provider to cancel one letter (pieceId from listContactDirectMail). It shows as canceled only once the provider confirms. Irreversible.

Sharing with partner organizations (WRITE scope)

Share contacts with an organization you have an active partnership with — see the Partnerships API and canShareWithPartners.

operationId What it does
shareContactWithPartner Share one contact with a partner organization (partnerOrganizationId), as the contact page’s Share does: the partner gets its own copy, assigned to the partner users in assignments. The include* flags choose what goes with it (core record, tasks, notes, calls, texts).
shareContactsWithPartner shareContactWithPartner for many contactIds. Async; returns a bulkTaskId.
unshareContactWithPartner Stop sharing a contact: the partner’s users lose access to their copy. Sharing it again creates a new copy, never the old one back.

Suggestion cards and Catch up (READ + WRITE scopes)

AI employees and Goliath leave suggestion cards on contacts — a drafted text or email reply, a note, a task, a booking, a fix to the record. These operations read and decide them as the app’s Catch up and contact card do. Catch up’s own cards are decided through decideCatchUpCard (see the Communications API).

operationId Scope What it does
listContactSuggestions READ A contact’s pending cards, newest first: what each proposes (action) and how sure it is. dirtyAt set means the conversation moved on since the card was written.
acceptContactSuggestion WRITE Accept a card as written. A reply card sends its text or email to the contact; any other card applies its action. Optional senderUserId, replyInThread, acceptPairedActions, acknowledgedWarning.
acceptEditedContactSuggestion WRITE Accept a text or email reply card with your own words (text) in place of the draft; it sends.
decideSuggestion WRITE Accept or dismiss any card in the suggestion feed — a contact’s, an AI employee’s, or an account card — by id (verb: ACCEPT or DISMISS). A dismiss takes reason and optional note; an account card’s accept runs only selectedProposalKeys and needs an org admin; a card with a question of its own takes its answer in decisions. Returns id, kind and status.
decideContactSuggestionsInBulk WRITE Accept or dismiss up to 500 cards by id, each as its own accept or dismiss would — an accept can send a reply per card. rowDecisions answers a card’s own question; dismissReason applies to a dismiss. Returns planned, applied, refused, truncated (only the first planned ran — call again) and unresolvedIds. An accept whose answer would spend credits is refused here.
dismissContactSuggestion WRITE Dismiss a card with a reason (NOT_RELEVANT, WRONG_TONE, WRONG_FACTS, ALREADY_DONE, OTHER, …).
markContactSuggestionHandled WRITE Close a card you already handled yourself (you sent the text, made the task): it is marked done without running its action, and a reply’s paired cards stay pending.
setAsideContactSuggestions WRITE Take cards out of an import’s bulk review; they stay pending on each contact. Nothing is decided or spent. Returns setAside and unresolvedIds.
commentOnSuggestionCard WRITE Leave feedback on a card (“too pushy”). Nothing changes or sends; the AI employee reads it when it drafts its next card. Send a new submissionKey (1–100 characters) per comment, and resend it unchanged on a retry.

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.

Example

Find a contact, then add a note to it:

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.