Contacts
Space MCP tools in the Contacts scope (contacts). Reach these with a wks_live_ key over space-mcp or space-api.
Tools in the contacts scope, callable over space-mcp (tools/call) or space-api (REST) with a wks_live_* key that grants contacts.
add_campaign_people_to_list
Scope: contacts:write
Put EVERYONE behind a campaign number into a contact list — an existing list (list_id, from list_audiences) or a new one (new_list_name) — e.g. "make a list of everyone who opened but never replied, for a follow-up campaign". A filter audience cannot take people by hand.
| Parameter | Type | Required | Description |
|---|---|---|---|
campaign_id | string | yes | |
metric | all | sent | delivered | opened | clicked | replied | unsubscribed | not_arrived | Which number: all (everyone in it) · sent · delivered · opened · clicked · replied · unsubscribed · not_arrived (bounced or refused). Default all. | |
email_number | number | One email of the campaign (1 = the first). Leave out for any of them — e.g. metric opened + email_number 2 = who opened email 2. | |
list_id | string | An existing LIST in this space. | |
new_list_name | string | Make a new list with this name instead. |
add_contact_note
Scope: contacts:write
Add a short note to a contact's Memory — the running log a human reads to remember a person. One plain sentence per note. Preferred way to 'remember X about' or 'log a call/message on' a contact.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes | |
body | string | yes | One short plain sentence. |
add_social_identity
Scope: contacts:write
Attach a social identity (Instagram/X/LinkedIn/Facebook/TikTok/etc.) to a contact. Provide handle (no leading @) and/or external_id. This is what powers inbound DM matching — prefer it over the legacy social_media JSONB.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes | |
platform | string | yes | |
handle | string | ||
external_id | string | ||
profile_url | string | ||
display_name | string |
bulk_tag_contacts
Scope: contacts:write
Add and/or remove tags across many contacts in ONE call. Pass contact_ids plus add_tag_ids and/or remove_tag_ids. Prefer this over looping tag_contact.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_ids | string[] | yes | |
add_tag_ids | string[] | ||
remove_tag_ids | string[] |
count_contacts
Scope: contacts:read
Count contacts matching structured filters (search/company/location/relationship_type/tag_ids/has_email/has_phone/auto_created/created_after/created_before). Cheaper than listing when you only need a number.
| Parameter | Type | Required | Description |
|---|---|---|---|
search | string | ||
company | string | ||
location | string | ||
line_of_work | string | ||
relationship_type | string | ||
tag_ids | string[] | ||
has_email | boolean | ||
has_phone | boolean | ||
auto_created | boolean | ||
email_permission | granted | not_given | unknown | Count by whether they agreed to hear from you. | |
created_after | string | ||
created_before | string |
create_audience
Scope: contacts:write
Create an audience (a list campaigns send to). Two kinds: picked people (contact_ids) or a FILTER that fills itself as contacts change (rules), e.g. everyone tagged lead in Austin: { combinator: "AND", conditions: [{ field: "tags", op: "contains_any", value: ["lead"] }, { field: "location", op: "contains", value: "Austin" }] }. Fields: tags (ops contains_any, contains_all, not_contains, is_empty, is_not_empty; value = tag names), email, name, first_name, last_name, company, phone, location, website, line_of_work, custom_fields.<key> (ops equals, not_equals, contains, not_contains, starts_with, ends_with, is_empty, is_not_empty), email_permission (ops equals, not_equals; value granted · not_given · unknown — e.g. everyone who agreed to hear from you), created_at, updated_at (ops before, after, between). Or from_audiences: [two or more audience ids] = anyone in any of them (what the campaign page makes when you pick several), then point the campaign at it. Only contacts with an email count. Audiences target both email and SMS campaigns.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | |
description | string | ||
contact_ids | string[] | Picked people. Leave out for a filter list. | |
from_audiences | string[] | Two or more audience ids: the new audience is anyone in any of them (use instead of contact_ids / rules). | |
rules | object | A filter list: { combinator: AND|OR, conditions: [{ field, op, value }] }. Leave out for picked people. |
create_contact
Scope: contacts:write
Create a new contact in the space. Only name is required (email is optional — social-DM/walk-in contacts have none). Name is split into first/last automatically; pass first_name/last_name explicitly for precise splitting. tags are matched/created by name.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Full display name, e.g. 'Sarah Lee'. |
first_name | string | Optional — overrides the auto-split first name. | |
middle_name | string | ||
last_name | string | Optional — overrides the auto-split last name. | |
username | string | ||
email | string | Optional. | |
additional_emails | string[] | ||
phone | string | ||
company | string | ||
line_of_work | string | Job title / role. | |
location | string | ||
website | string | ||
birthday | string | ISO date YYYY-MM-DD. | |
avatar | string | Avatar image URL. | |
bio | string | ||
relationship_type | string | e.g. 'lead', 'client', 'contact', 'partner'. | |
how_we_met | string | ||
interests | string[] | ||
personal_notes | string | ||
social_media | object | JSONB map of platform → handle. | |
tags | string[] | Tag names — matched case-insensitively, created if missing. | |
custom_fields | object | ||
email_permission | granted | not_given | unknown | Whether they agreed to hear from the space by email. granted needs permission_note saying how; not_given keeps every newsletter from them. Written into their consent history under your name. | |
permission_note | string | How you know, e.g. "signed up at our stand on 3 May", "said no on the phone". |
create_contact_section
Scope: contacts:write
Add a content section to a contact (documents/links/custom boxes). For simple 'remember X' notes prefer add_contact_note. section_type: notes | documents | links | insights | custom.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes | |
title | string | yes | |
section_type | string | ||
content | string | ||
metadata | object | ||
sort_order | number |
create_tag
Scope: contacts:write
Create a new contact tag. Colors: gray, red, orange, amber, yellow, green, teal, blue, indigo, purple, pink.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | |
color | string | ||
description | string | ||
icon | string |
delete_contact
Scope: contacts:write
Permanently delete ONE contact by its id (cascades to its tags, notes, sections, and social identities). Destructive and irreversible — only call with an explicit contact_id the user unambiguously asked to delete. There is no bulk/filter delete; delete one id at a time.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes |
delete_contact_section
Scope: contacts:write
Permanently delete a contact section. Destructive.
| Parameter | Type | Required | Description |
|---|---|---|---|
section_id | string | yes |
delete_tag
Scope: contacts:write
Permanently delete a tag from this space (removes it from every contact). Destructive.
| Parameter | Type | Required | Description |
|---|---|---|---|
tag_id | string | yes |
erase_contact
Scope: contacts:write
Consequential and permanent: erase a person who asked to be forgotten — their emails and conversations, campaign places, form answers, bookings, notes, audiences, agent memory, consent history and the contact itself. Orders and payments stay, unlinked. Their address is kept only as a one-way hash so nobody emails them again by mistake, and a record without their name goes to Space Settings → Privacy. The owner approves it in this chat every time. confirm = their email address exactly (or their name when they have no email). Offer export_contact_data first if they also asked for a copy.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes | |
confirm | string | yes | Their email address, exactly — or their name when there is no email. |
reason | string | Why, in a sentence ("asked by email on 26 Sep"). |
export_contact_data
Scope: contacts:read
Everything this space holds about one person, as one document — their details, tags, audiences, notes, form answers, bookings, every email to and from them, orders, what agents remember, and their consent history. Use it when someone asks for a copy of their data; hand it to the person who asked for them.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes |
get_contact
Scope: contacts:read
Fetch ONE contact by id with everything attached: all columns, tags, social identities, and recent sections. Use this to read a contact's full profile before editing.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes |
get_contact_consent
Scope: contacts:read
How a contact came to hear from this space and every change since: signed up (which form, the page, the ip), confirmed their email, imported (with what the importer said about permission), added to an audience (by whom), unsubscribed or came back (which campaign), stopped all emails, permission set — plus their email permission (granted · not_given · unknown) and the campaigns they are in or left. Read it before mailing someone whose permission you are unsure of; a contact marked not_given gets no newsletter.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes |
import_contacts
Scope: contacts:write
Add up to 500 people at once — a lead list you researched, a list someone gave you. Each person is matched by email: someone the space already has is UPDATED (blanks filled, custom fields merged, tags added), never added twice; someone erased at their own request is skipped. Put each person's personal lines in custom_fields (e.g. {"reason_line": "Saw you're hiring two SDRs"}) — an email step writes {{reason_line}} and it fills from the contact. Keys are stored as lowercase_with_underscores. People found by research have email_permission "unknown" (the default): say where they came from in lead_source. Answers counts, what was skipped and why, and contact_ids in row order — hand them to add_people_to_campaign.
| Parameter | Type | Required | Description |
|---|---|---|---|
contacts | object[] | yes | The people. name (or first_name + last_name) is required; email is how they are matched. |
tags | string[] | Tags on everyone in this call, e.g. ["Outbound: dental clinics TX"]. Created if missing. | |
custom_fields | object | Values for everyone (a person's own values win). | |
relationship_type | string | Default "lead". | |
lead_source | string | Where they came from, e.g. "apollo", "google_maps", "event list". Default "import". | |
email_permission | granted | not_given | unknown | For everyone. People found by research are "unknown" (the default). "granted" needs permission_note saying how they agreed. An answer a person already gave is never changed by an import. | |
permission_note | string | ||
on_existing | update | skip | update (default): fill blanks, merge custom fields, add tags. skip: leave people the space already has as they are. |
list_audiences
Scope: contacts:read
List audiences (saved contact groups used to target email and SMS campaigns).
list_contact_erasures
Scope: contacts:read
The space's record of people erased on request: when, by whom, why and what was taken — never their name or address.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | Default 100. |
list_contact_notes
Scope: contacts:read
List a contact's Memory notes, newest first.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes | |
limit | number | Max notes (default 20). |
list_contact_sections
Scope: contacts:read
List the content sections (notes/documents/links/insights/custom) attached to a contact.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes | |
section_type | string |
list_contacts
Scope: contacts:read
List contacts in the space. Supports search and pagination.
| Parameter | Type | Required | Description |
|---|---|---|---|
search | string | Match against name/email/company. | |
email_permission | granted | not_given | unknown | Only people who agreed to hear from you (granted), who did not (not_given), or whose permission is not recorded (unknown). | |
limit | number | ||
offset | number |
list_tags
Scope: contacts:read
List the contact tags in this space (id, name, color, description). Call before tagging to resolve tag names → ids.
merge_contacts
Scope: contacts:write
Merge two duplicate contacts: fold source_contact_id into target_contact_id. All threads, tags, sections, and social identities move to the target, then the source row is deleted. Prefer this over delete for de-duplication. Destructive — confirm before merging curated contacts.
| Parameter | Type | Required | Description |
|---|---|---|---|
source_contact_id | string | yes | The duplicate that will be deleted. |
target_contact_id | string | yes | The contact that survives and inherits everything. |
preview_contact_erase
Scope: contacts:read
What erasing a contact would take (emails, conversations, campaign places, form answers, bookings, notes, audiences, agent memory) and what it keeps (orders and payments, the shop inbox, jobs — the law asks a business to keep those). Read before erase_contact.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes |
remove_social_identity
Scope: contacts:write
Delete a social identity row from a contact.
| Parameter | Type | Required | Description |
|---|---|---|---|
identity_id | string | yes |
set_contact_email_permission
Scope: contacts:write
Record whether this space may email a contact in bulk: 'granted' (they said yes — say how in note), 'not_given' (no newsletter or campaign reaches them until it changes) or null (unknown). It lands in their consent history with your name and the note. Never mark 'granted' without a real yes from the person.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes | |
permission | granted | not_given | null | yes | |
note | string | How you know, in a sentence ("said yes on our call of 3 May"). |
tag_campaign_people
Scope: contacts:write
Tag EVERYONE behind a campaign number with one contact tag, e.g. everyone who clicked email 2 → "warm", everyone who never opened → "cold". The tag is made if the space does not have it. People with no contact in this space are left out and counted.
| Parameter | Type | Required | Description |
|---|---|---|---|
campaign_id | string | yes | |
metric | all | sent | delivered | opened | clicked | replied | unsubscribed | not_arrived | Which number: all (everyone in it) · sent · delivered · opened · clicked · replied · unsubscribed · not_arrived (bounced or refused). Default all. | |
email_number | number | One email of the campaign (1 = the first). Leave out for any of them — e.g. metric opened + email_number 2 = who opened email 2. | |
tag | string | yes | The tag's name. |
tag_contact
Scope: contacts:write
Attach one tag to one contact.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes | |
tag_id | string | yes |
untag_contact
Scope: contacts:write
Detach one tag from one contact.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes | |
tag_id | string | yes |
update_audience
Scope: contacts:write
Rename an audience or add/remove member contacts.
| Parameter | Type | Required | Description |
|---|---|---|---|
audience_id | string | yes | |
name | string | ||
description | string | ||
add_contact_ids | string[] | ||
remove_contact_ids | string[] |
update_contact
Scope: contacts:write
Update fields on an existing contact. Only supplied fields change (pass null to clear one). Pass tag_ids to REPLACE the entire tag set (omit to leave tags unchanged). Get the contact first to resolve its id.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes | |
name | string | ||
first_name | string | ||
middle_name | string | ||
last_name | string | ||
username | string | ||
email | string | ||
additional_emails | string[] | ||
phone | string | ||
company | string | ||
line_of_work | string | ||
location | string | ||
website | string | ||
birthday | string | ||
avatar | string | ||
bio | string | ||
relationship_type | string | ||
how_we_met | string | ||
interests | string[] | ||
personal_notes | string | ||
social_media | object | ||
custom_fields | object | ||
tag_ids | string[] | REPLACES the full tag set. Resolve ids via list_tags. | |
email_permission | granted | not_given | unknown | Whether they agreed to hear from the space by email. granted needs permission_note saying how; not_given keeps every newsletter from them. Written into their consent history under your name. | |
permission_note | string | How you know, e.g. "signed up at our stand on 3 May", "said no on the phone". |
update_contact_section
Scope: contacts:write
Update an existing contact section's title, content, type, metadata, or sort_order.
| Parameter | Type | Required | Description |
|---|---|---|---|
section_id | string | yes | |
title | string | ||
section_type | string | ||
content | string | ||
metadata | object | ||
sort_order | number |
update_social_identity
Scope: contacts:write
Update an existing social identity row (handle, external_id, profile_url, display_name).
| Parameter | Type | Required | Description |
|---|---|---|---|
identity_id | string | yes | |
handle | string | ||
external_id | string | ||
profile_url | string | ||
display_name | string |
update_tag
Scope: contacts:write
Rename or restyle an existing tag.
| Parameter | Type | Required | Description |
|---|---|---|---|
tag_id | string | yes | |
name | string | ||
color | string | ||
description | string | ||
icon | string |