WorkloomDevelopers
v1Get an API key
Reference/Contacts

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

idstring

The contact’s id.

first_namestring, nullable

First name.

last_namestring, nullable

Last name.

full_namestring, nullable

The name as one string.

emailstring, nullable

Work email.

phonestring, nullable

Phone number, as entered.

titlestring, nullable

Job title.

company_namestring, nullable

Where they work.

company_domainstring, nullable

Their company’s website domain.

company_idstring, nullable

The company record they belong to, if any.

linkedin_urlstring, nullable

LinkedIn profile address.

locationobject, nullable

Where they are, as far as we know.

owner_seat_idstring, nullable

The teammate who owns this contact. Null when nobody does.

tagsarray of string

Tags on the contact.

custom_fieldsobject

Your 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_attimestamp

When the contact was added.

updated_attimestamp

When the contact last changed.

The contact object
{
  "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

GET/contacts

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.

Scope: contacts:read
Query parameters
emailstring

Only the contact with this email.

updated_sincetimestamp

Only contacts changed at or after this time (ISO 8601).

sortstring

Order 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.

limitinteger

How many to return, 1 to 100. Default 25.

cursorstring

The next_cursor from the previous page.

Errors
400

invalid_request, unknown_fields, reason_required or unsupported_version.

401

authentication_required or invalid_key.

403

missing_scope, forbidden, seat_inactive or organisation_inactive.

429

rate_limited. Wait Retry-After seconds.

500

internal_error.

List contacts
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()
Response
{
  "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"
}
200 OKsample
POST/contacts

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.

Scope: contacts:writeTakes Idempotency-Key
Header parameters
Idempotency-Keystring

Any 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.

Body parameters
first_namestring, nullable

First name.

last_namestring, nullable

Last name.

full_namestring, nullable

The whole name, when you do not have it split.

emailstring, nullable

Work email.

phonestring, nullable

Phone number.

titlestring, nullable

Job title.

company_namestring, nullable

Where they work.

company_idstring, nullable

A company record in your workspace.

linkedin_urlstring, nullable

LinkedIn profile address.

custom_fieldsobject

Your own fields by key (GET /custom-fields). Each value is checked against its field’s type, and everything is checked before anything is saved.

forceboolean

Add them even though someone with the same name works at the same company.

Errors
400

invalid_request, unknown_fields, reason_required or unsupported_version.

401

authentication_required or invalid_key.

403

missing_scope, forbidden, seat_inactive or organisation_inactive.

409

duplicate, conflict or idempotency_key_in_use.

422

idempotency_key_reused.

429

rate_limited. Wait Retry-After seconds.

500

internal_error.

Add a contact
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()
Response
{
  "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
}
201 Createdsample
GET/contacts/{id}

Get a contact

Returns one contact. A contact the key cannot reach answers not_found, the same as one that does not exist.

Scope: contacts:read
Path parameters
idstringRequired

The contact’s id.

Errors
400

invalid_request, unknown_fields, reason_required or unsupported_version.

401

authentication_required or invalid_key.

403

missing_scope, forbidden, seat_inactive or organisation_inactive.

404

not_found.

429

rate_limited. Wait Retry-After seconds.

500

internal_error.

Get a contact
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()
Response
{
  "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"
  }
}
200 OKsample
PATCH/contacts/{id}

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.

Scope: contacts:write
Path parameters
idstringRequired

The contact’s id.

Body parameters
full_namestring, nullable

The whole name.

titlestring, nullable

Job title.

emailstring, nullable

Work email.

phonestring, nullable

Phone number.

company_namestring, nullable

Where they work.

locationstring, nullable

Where they are, as one line.

senioritystring

Seniority.

departmentstring, nullable

Department.

linkedin_urlstring, nullable

LinkedIn profile address.

timezonestring, nullable

IANA time zone, for example Asia/Kolkata.

tagsarray of string

Replaces the tags.

owner_seat_idstring, nullable

Hand the contact to a teammate. Null leaves it unowned.

custom_fieldsobject

Your own fields by key (GET /custom-fields). Each value is checked against its field’s type, and everything is checked before anything is saved.

Errors
400

invalid_request, unknown_fields, reason_required or unsupported_version.

401

authentication_required or invalid_key.

403

missing_scope, forbidden, seat_inactive or organisation_inactive.

404

not_found.

409

duplicate, conflict or idempotency_key_in_use.

429

rate_limited. Wait Retry-After seconds.

500

internal_error.

Update a contact
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()
Response
{
  "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
}
200 OKsample
DELETE/contacts/{id}

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.

Scope: contacts:writeDeletes
Path parameters
idstringRequired

The contact’s id.

Errors
400

invalid_request, unknown_fields, reason_required or unsupported_version.

401

authentication_required or invalid_key.

403

missing_scope, forbidden, seat_inactive or organisation_inactive.

404

not_found.

409

duplicate, conflict or idempotency_key_in_use.

429

rate_limited. Wait Retry-After seconds.

500

internal_error.

Delete a contact
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()
Response
{
  "data": {
    "id": "66f2a1c0e4b0a1b2c3d4e5f6",
    "deleted": true
  },
  "livemode": true
}
200 OKsample
POST/contacts/upsert

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.

Scope: contacts:writeTakes Idempotency-Key
Header parameters
Idempotency-Keystring

Any 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.

Body parameters
emailstringRequired

Work email. The match key, case-insensitive.

first_namestring, nullable

First name. Used when adding.

last_namestring, nullable

Last name. Used when adding.

full_namestring, nullable

The whole name.

phonestring, nullable

Phone number.

titlestring, nullable

Job title.

company_namestring, nullable

Where they work.

company_idstring, nullable

A company record in your workspace. Used when adding.

linkedin_urlstring, nullable

LinkedIn profile address.

locationstring, nullable

Where they are, as one line. Used when updating.

senioritystring

Seniority. Used when updating.

departmentstring, nullable

Department. Used when updating.

timezonestring, nullable

IANA time zone. Used when updating.

tagsarray of string

Replaces the tags.

owner_seat_idstring, nullable

Hand the contact to a teammate.

custom_fieldsobject

Your own fields by key (GET /custom-fields). Each value is checked against its field’s type, and everything is checked before anything is saved.

Errors
400

invalid_request, unknown_fields, reason_required or unsupported_version.

401

authentication_required or invalid_key.

403

missing_scope, forbidden, seat_inactive or organisation_inactive.

409

duplicate, conflict or idempotency_key_in_use.

422

idempotency_key_reused.

429

rate_limited. Wait Retry-After seconds.

500

internal_error.

Add or update a contact by email
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()
Response
{
  "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
}
200 OKsample
Was this page useful?