Contacts
A contact is one person in your workspace. Adding one over the API runs the same checks as adding one in Workloom: the same email or LinkedIn profile is refused, and your routing rules run for the new contact. A deleted contact stays on the do-not-contact list.
Scopes: contacts:read to read, contacts:write to change.
The contact object
idstringThe contact’s id.
first_namestring, nullableFirst name.
last_namestring, nullableLast name.
full_namestring, nullableThe name as one string.
emailstring, nullableWork email.
phonestring, nullablePhone number, as entered.
titlestring, nullableJob title.
company_namestring, nullableWhere they work.
company_domainstring, nullableTheir company’s website domain.
company_idstring, nullableThe company record they belong to, if any.
linkedin_urlstring, nullableLinkedIn profile address.
locationobject, nullableWhere they are, as far as we know.
owner_seat_idstring, nullableThe teammate who owns this contact. Null when nobody does.
tagsarray of stringTags on the contact.
custom_fieldsobjectYour own fields (Settings › Data model), by key. GET /custom-fields lists their keys, labels and types. A field with no value is left out.
created_attimestampWhen the contact was added.
updated_attimestampWhen the contact last changed.
{
"id": "66f2a1c0e4b0a1b2c3d4e5f6",
"first_name": "Priya",
"last_name": "Nair",
"full_name": "Priya Nair",
"email": "priya@brightline.example",
"phone": "+91 98200 11210",
"title": "Head of Sales",
"company_name": "Brightline",
"company_domain": "brightline.example",
"company_id": "66f19b77e4b0a1b2c3d4a1b2",
"linkedin_url": "https://www.linkedin.com/in/priyanair",
"location": {
"city": "Bengaluru",
"state": "Karnataka",
"country": "India"
},
"owner_seat_id": "66e0f1a2e4b0a1b2c3d40a0b",
"tags": [
"ops-leaders",
"series-b"
],
"custom_fields": {
"region": "South",
"segment": "Mid-market"
},
"created_at": "2026-09-21T10:12:44.000Z",
"updated_at": "2026-10-03T08:41:09.000Z"
}Endpoints
List contacts
Contacts in your workspace, oldest first unless you sort. A key acting for one person sees only the contacts that person can see.
emailstringOnly the contact with this email.
updated_sincetimestampOnly contacts changed at or after this time (ISO 8601).
sortstringOrder by one field, oldest or smallest first; put - in front for newest or largest first. Empty values come first ascending and last descending. Default: oldest first. Keep the same sort while you follow next_cursor.
limitintegerHow many to return, 1 to 100. Default 25.
cursorstringThe next_cursor from the previous page.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl "https://api.useworkloom.com/api/public/v1/contacts?limit=25" \
-H "Authorization: Bearer $WORKLOOM_KEY"const res = await fetch("https://api.useworkloom.com/api/public/v1/contacts?limit=25", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`
}
});
const data = await res.json();import os, requests
res = requests.get(
"https://api.useworkloom.com/api/public/v1/contacts?limit=25",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
)
data = res.json(){
"data": [
{
"id": "66f2a1c0e4b0a1b2c3d4e5f6",
"first_name": "Priya",
"last_name": "Nair",
"full_name": "Priya Nair",
"email": "priya@brightline.example",
"phone": "+91 98200 11210",
"title": "Head of Sales",
"company_name": "Brightline",
"company_domain": "brightline.example",
"company_id": "66f19b77e4b0a1b2c3d4a1b2",
"linkedin_url": "https://www.linkedin.com/in/priyanair",
"location": {
"city": "Bengaluru",
"state": "Karnataka",
"country": "India"
},
"owner_seat_id": "66e0f1a2e4b0a1b2c3d40a0b",
"tags": [
"ops-leaders",
"series-b"
],
"custom_fields": {
"region": "South",
"segment": "Mid-market"
},
"created_at": "2026-09-21T10:12:44.000Z",
"updated_at": "2026-10-03T08:41:09.000Z"
}
],
"has_more": true,
"next_cursor": "66f2a1c0e4b0a1b2c3d4e5f6"
}Add a contact
Adds a contact, checked for duplicates exactly as adding one in Workloom is. A key acting for the workspace leaves the new contact unowned; a key acting for a person makes it theirs. Your routing rules run as for any new contact.
Idempotency-KeystringAny unique string, up to 255 characters. Send the same key when you retry and the request runs once; the first answer comes back with Idempotent-Replayed: true. Kept 24 hours.
first_namestring, nullableFirst name.
last_namestring, nullableLast name.
full_namestring, nullableThe whole name, when you do not have it split.
emailstring, nullableWork email.
phonestring, nullablePhone number.
titlestring, nullableJob title.
company_namestring, nullableWhere they work.
company_idstring, nullableA company record in your workspace.
linkedin_urlstring, nullableLinkedIn profile address.
custom_fieldsobjectYour own fields by key (GET /custom-fields). Each value is checked against its field’s type, and everything is checked before anything is saved.
forcebooleanAdd them even though someone with the same name works at the same company.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
409duplicate, conflict or idempotency_key_in_use.
422idempotency_key_reused.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl -X POST "https://api.useworkloom.com/api/public/v1/contacts" \
-H "Authorization: Bearer $WORKLOOM_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"first_name": "Priya",
"last_name": "Nair",
"email": "priya@brightline.example",
"title": "Head of Sales",
"company_name": "Brightline"
}'const res = await fetch("https://api.useworkloom.com/api/public/v1/contacts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID()
},
body: JSON.stringify({
"first_name": "Priya",
"last_name": "Nair",
"email": "priya@brightline.example",
"title": "Head of Sales",
"company_name": "Brightline"
})
});
const data = await res.json();import os, uuid, requests
res = requests.post(
"https://api.useworkloom.com/api/public/v1/contacts",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}", "Idempotency-Key": str(uuid.uuid4())},
json={
"first_name": "Priya",
"last_name": "Nair",
"email": "priya@brightline.example",
"title": "Head of Sales",
"company_name": "Brightline"
},
)
data = res.json(){
"data": {
"id": "66f2a1c0e4b0a1b2c3d4e5f6",
"first_name": "Priya",
"last_name": "Nair",
"full_name": "Priya Nair",
"email": "priya@brightline.example",
"phone": "+91 98200 11210",
"title": "Head of Sales",
"company_name": "Brightline",
"company_domain": "brightline.example",
"company_id": "66f19b77e4b0a1b2c3d4a1b2",
"linkedin_url": "https://www.linkedin.com/in/priyanair",
"location": {
"city": "Bengaluru",
"state": "Karnataka",
"country": "India"
},
"owner_seat_id": "66e0f1a2e4b0a1b2c3d40a0b",
"tags": [
"ops-leaders",
"series-b"
],
"custom_fields": {
"region": "South",
"segment": "Mid-market"
},
"created_at": "2026-09-21T10:12:44.000Z",
"updated_at": "2026-10-03T08:41:09.000Z"
},
"livemode": true
}Get a contact
Returns one contact. A contact the key cannot reach answers not_found, the same as one that does not exist.
idstringRequiredThe contact’s id.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
404not_found.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl "https://api.useworkloom.com/api/public/v1/contacts/66f2a1c0e4b0a1b2c3d4e5f6" \
-H "Authorization: Bearer $WORKLOOM_KEY"const res = await fetch("https://api.useworkloom.com/api/public/v1/contacts/66f2a1c0e4b0a1b2c3d4e5f6", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`
}
});
const data = await res.json();import os, requests
res = requests.get(
"https://api.useworkloom.com/api/public/v1/contacts/66f2a1c0e4b0a1b2c3d4e5f6",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
)
data = res.json(){
"data": {
"id": "66f2a1c0e4b0a1b2c3d4e5f6",
"first_name": "Priya",
"last_name": "Nair",
"full_name": "Priya Nair",
"email": "priya@brightline.example",
"phone": "+91 98200 11210",
"title": "Head of Sales",
"company_name": "Brightline",
"company_domain": "brightline.example",
"company_id": "66f19b77e4b0a1b2c3d4a1b2",
"linkedin_url": "https://www.linkedin.com/in/priyanair",
"location": {
"city": "Bengaluru",
"state": "Karnataka",
"country": "India"
},
"owner_seat_id": "66e0f1a2e4b0a1b2c3d40a0b",
"tags": [
"ops-leaders",
"series-b"
],
"custom_fields": {
"region": "South",
"segment": "Mid-market"
},
"created_at": "2026-09-21T10:12:44.000Z",
"updated_at": "2026-10-03T08:41:09.000Z"
}
}Update a contact
Changes only the fields you send. Every field is checked before any is saved, so a request that fails changes nothing. Changing owner_seat_id needs a key that acts for the whole workspace, or a role that reassigns records across it.
idstringRequiredThe contact’s id.
full_namestring, nullableThe whole name.
titlestring, nullableJob title.
emailstring, nullableWork email.
phonestring, nullablePhone number.
company_namestring, nullableWhere they work.
locationstring, nullableWhere they are, as one line.
senioritystringSeniority.
departmentstring, nullableDepartment.
linkedin_urlstring, nullableLinkedIn profile address.
timezonestring, nullableIANA time zone, for example Asia/Kolkata.
tagsarray of stringReplaces the tags.
owner_seat_idstring, nullableHand the contact to a teammate. Null leaves it unowned.
custom_fieldsobjectYour own fields by key (GET /custom-fields). Each value is checked against its field’s type, and everything is checked before anything is saved.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
404not_found.
409duplicate, conflict or idempotency_key_in_use.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl -X PATCH "https://api.useworkloom.com/api/public/v1/contacts/66f2a1c0e4b0a1b2c3d4e5f6" \
-H "Authorization: Bearer $WORKLOOM_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "VP Sales",
"tags": [
"ops-leaders",
"series-b",
"demo-booked"
]
}'const res = await fetch("https://api.useworkloom.com/api/public/v1/contacts/66f2a1c0e4b0a1b2c3d4e5f6", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"title": "VP Sales",
"tags": [
"ops-leaders",
"series-b",
"demo-booked"
]
})
});
const data = await res.json();import os, requests
res = requests.patch(
"https://api.useworkloom.com/api/public/v1/contacts/66f2a1c0e4b0a1b2c3d4e5f6",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
json={
"title": "VP Sales",
"tags": [
"ops-leaders",
"series-b",
"demo-booked"
]
},
)
data = res.json(){
"data": {
"id": "66f2a1c0e4b0a1b2c3d4e5f6",
"first_name": "Priya",
"last_name": "Nair",
"full_name": "Priya Nair",
"email": "priya@brightline.example",
"phone": "+91 98200 11210",
"title": "Head of Sales",
"company_name": "Brightline",
"company_domain": "brightline.example",
"company_id": "66f19b77e4b0a1b2c3d4a1b2",
"linkedin_url": "https://www.linkedin.com/in/priyanair",
"location": {
"city": "Bengaluru",
"state": "Karnataka",
"country": "India"
},
"owner_seat_id": "66e0f1a2e4b0a1b2c3d40a0b",
"tags": [
"ops-leaders",
"series-b"
],
"custom_fields": {
"region": "South",
"segment": "Mid-market"
},
"created_at": "2026-09-21T10:12:44.000Z",
"updated_at": "2026-10-03T08:41:09.000Z"
},
"livemode": true
}Delete a contact
Deletes the contact and keeps them suppressed: every channel is opted out, their sequences stop and they leave every weave, as "Do not contact" does in Workloom. The same email cannot be added again. A key acting for one person needs that person’s permission to delete records.
idstringRequiredThe contact’s id.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
404not_found.
409duplicate, conflict or idempotency_key_in_use.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl -X DELETE "https://api.useworkloom.com/api/public/v1/contacts/66f2a1c0e4b0a1b2c3d4e5f6" \
-H "Authorization: Bearer $WORKLOOM_KEY"const res = await fetch("https://api.useworkloom.com/api/public/v1/contacts/66f2a1c0e4b0a1b2c3d4e5f6", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`
}
});
const data = await res.json();import os, requests
res = requests.delete(
"https://api.useworkloom.com/api/public/v1/contacts/66f2a1c0e4b0a1b2c3d4e5f6",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
)
data = res.json(){
"data": {
"id": "66f2a1c0e4b0a1b2c3d4e5f6",
"deleted": true
},
"livemode": true
}Search contacts
Contacts matching every filter, oldest first unless you sort (created_at or updated_at), paged like the list. Searchable: email, first_name, last_name, full_name, phone, title, company_name, company_domain, company_id, linkedin_url, location.city, location.country, owner_seat_id, tags, created_at, updated_at, and custom_fields.<key>. Operators: eq, neq, in, gt, gte, lt, lte (numbers and dates), contains (text, case-insensitive; on tags, has the tag), exists.
filtersarray of SearchFilterUp to 20 filters. All must match.
limitintegerHow many to return, 1 to 100. Default 25.
cursorstring, nullableThe next_cursor from the previous page.
sortstring, nullableOrder by one field, as sort on the list: - in front for newest or largest first. Keep the same sort while you follow next_cursor.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
409duplicate, conflict or idempotency_key_in_use.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl -X POST "https://api.useworkloom.com/api/public/v1/contacts/search" \
-H "Authorization: Bearer $WORKLOOM_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": [
{
"field": "tags",
"op": "contains",
"value": "series-b"
},
{
"field": "updated_at",
"op": "gte",
"value": "2026-10-01"
}
],
"limit": 50
}'const res = await fetch("https://api.useworkloom.com/api/public/v1/contacts/search", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"filters": [
{
"field": "tags",
"op": "contains",
"value": "series-b"
},
{
"field": "updated_at",
"op": "gte",
"value": "2026-10-01"
}
],
"limit": 50
})
});
const data = await res.json();import os, requests
res = requests.post(
"https://api.useworkloom.com/api/public/v1/contacts/search",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
json={
"filters": [
{
"field": "tags",
"op": "contains",
"value": "series-b"
},
{
"field": "updated_at",
"op": "gte",
"value": "2026-10-01"
}
],
"limit": 50
},
)
data = res.json(){
"data": [
{
"id": "66f2a1c0e4b0a1b2c3d4e5f6",
"first_name": "Priya",
"last_name": "Nair",
"full_name": "Priya Nair",
"email": "priya@brightline.example",
"phone": "+91 98200 11210",
"title": "Head of Sales",
"company_name": "Brightline",
"company_domain": "brightline.example",
"company_id": "66f19b77e4b0a1b2c3d4a1b2",
"linkedin_url": "https://www.linkedin.com/in/priyanair",
"location": {
"city": "Bengaluru",
"state": "Karnataka",
"country": "India"
},
"owner_seat_id": "66e0f1a2e4b0a1b2c3d40a0b",
"tags": [
"ops-leaders",
"series-b"
],
"custom_fields": {
"region": "South",
"segment": "Mid-market"
},
"created_at": "2026-09-21T10:12:44.000Z",
"updated_at": "2026-10-03T08:41:09.000Z"
}
],
"has_more": true,
"next_cursor": "66f2a1c0e4b0a1b2c3d4e5f6"
}Add or update a contact by email
Changes the contact with this email, or adds one when there is none. result says which. A contact with the same email that a teammate owns, outside the key’s reach, is a duplicate; one that was deleted and suppressed is suppressed.
Idempotency-KeystringAny unique string, up to 255 characters. Send the same key when you retry and the request runs once; the first answer comes back with Idempotent-Replayed: true. Kept 24 hours.
emailstringRequiredWork email. The match key, case-insensitive.
first_namestring, nullableFirst name. Used when adding.
last_namestring, nullableLast name. Used when adding.
full_namestring, nullableThe whole name.
phonestring, nullablePhone number.
titlestring, nullableJob title.
company_namestring, nullableWhere they work.
company_idstring, nullableA company record in your workspace. Used when adding.
linkedin_urlstring, nullableLinkedIn profile address.
locationstring, nullableWhere they are, as one line. Used when updating.
senioritystringSeniority. Used when updating.
departmentstring, nullableDepartment. Used when updating.
timezonestring, nullableIANA time zone. Used when updating.
tagsarray of stringReplaces the tags.
owner_seat_idstring, nullableHand the contact to a teammate.
custom_fieldsobjectYour own fields by key (GET /custom-fields). Each value is checked against its field’s type, and everything is checked before anything is saved.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
409duplicate, conflict or idempotency_key_in_use.
422idempotency_key_reused.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl -X POST "https://api.useworkloom.com/api/public/v1/contacts/upsert" \
-H "Authorization: Bearer $WORKLOOM_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"email": "priya@brightline.example",
"title": "VP Sales",
"custom_fields": {
"region": "South"
}
}'const res = await fetch("https://api.useworkloom.com/api/public/v1/contacts/upsert", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID()
},
body: JSON.stringify({
"email": "priya@brightline.example",
"title": "VP Sales",
"custom_fields": {
"region": "South"
}
})
});
const data = await res.json();import os, uuid, requests
res = requests.post(
"https://api.useworkloom.com/api/public/v1/contacts/upsert",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}", "Idempotency-Key": str(uuid.uuid4())},
json={
"email": "priya@brightline.example",
"title": "VP Sales",
"custom_fields": {
"region": "South"
}
},
)
data = res.json(){
"data": {
"id": "66f2a1c0e4b0a1b2c3d4e5f6",
"first_name": "Priya",
"last_name": "Nair",
"full_name": "Priya Nair",
"email": "priya@brightline.example",
"phone": "+91 98200 11210",
"title": "Head of Sales",
"company_name": "Brightline",
"company_domain": "brightline.example",
"company_id": "66f19b77e4b0a1b2c3d4a1b2",
"linkedin_url": "https://www.linkedin.com/in/priyanair",
"location": {
"city": "Bengaluru",
"state": "Karnataka",
"country": "India"
},
"owner_seat_id": "66e0f1a2e4b0a1b2c3d40a0b",
"tags": [
"ops-leaders",
"series-b"
],
"custom_fields": {
"region": "South",
"segment": "Mid-market"
},
"created_at": "2026-09-21T10:12:44.000Z",
"updated_at": "2026-10-03T08:41:09.000Z"
},
"result": "updated",
"livemode": true
}