Base URL and access
X-API-KEY header, same as the rest of the public API. Your workspace is enabled for the People API when your account has the Clay integration turned on — this is the same integration that gates the API key panel (labeled “RevyOps API”) under a client’s Integrations page in the dashboard. If it isn’t enabled, every People endpoint returns 403 Forbidden, regardless of whether your API key is otherwise valid.
There is no v1/v2 split for People — there’s a single version, and unlike the v1 Contacts/Companies endpoints, an empty search always returns the standard paginated envelope with results: [], never the {"status": "No contacts found"} shape described in the API introduction.
The Person object
Every read endpoint (search, lookup, get) returns Person records in this shape:first_name and last_name come from whichever linked Contact the database happens to return first — there’s no explicit ordering, so if two Contacts on the same Person disagree on name, which one wins isn’t guaranteed. In practice this rarely matters, since most People have one canonical name across all their Contacts.emails, linkedin_urls, and phones shares the same shape — the value field holds the email address, LinkedIn URL, or phone number respectively:
custom_fields entries look like:
Deduplication only collapses a
(field_name, field_value) pair that repeats identically across multiple linked Contacts. If two Contacts on the same Person hold different values for the same field name, both values show up as separate custom_fields entries — the API doesn’t try to pick a winner.
Here’s a full example:
Search people
Combining filters narrows the result set (they’re ANDed) — e.g.
firstname=Priya&domain=acme.io returns only People with at least one Contact named Priya and at least one Contact at acme.io (not necessarily the same Contact).
A search response looks like this:
count is the number of matching Person groupings, not the number of underlying Contacts. results can occasionally come back shorter than a page implies — the endpoint silently drops any Person id it can’t fully resolve when assembling the page, rather than erroring.
Lookup a person
email, linkedin_url, or phone — never more than one, never zero.
If you provide zero identifiers, or more than one, this returns:
phone value that can’t be parsed at all — this returns 404:
results array — with 200.
Get a person by ID
Returns a Person object on
200. Returns 404 if no Person with that id exists for your workspace, and also 404 — with the same {"detail": "No people record found."} body — in the edge case where the Person row exists but currently has zero linked Contacts (for example, if every Contact under it was individually deleted through the Contacts API).
Merge people
Request:
200:
- Every Contact belonging to
person_ids[1:](the “secondary” people) is reassigned toperson_ids[0](the primary). - The secondary Person rows are deleted outright.
- The primary is queued for an async rescore, since it may have inherited campaign activity its cached score doesn’t reflect yet.
person_ids doesn’t correspond to a Person in your workspace, nothing is changed and you get:
Split a person
The Person object doesn’t expose Contact ids anywhere in its
emails/linkedin_urls/phones entries, so you can’t get a contact_id to split from a People API response alone. Look it up first via the Contacts API — e.g. GET /api/public/contacts?email=priya.nair@acme.io — to get the Contact’s id, then pass that id here.200:
new_person_id), and every Contact id listed in contact_ids is reassigned from source_person_id to it. Nothing else about those Contacts changes.
If contact_ids includes every Contact currently on person_id — i.e. the split would leave the source Person with zero Contacts — the request is rejected and nothing changes:
person_id doesn’t exist for your workspace, or any id in contact_ids isn’t currently linked to person_id, you get:
Error summary
Note that search never 404s — an unmatched search just returns
count: 0 with an empty results array.