People
Match your records to Tahoe people by email or LinkedIn URL.
The same human can be in Tahoe as an applicant, a sourced profile and a shared-pool profile at once. These endpoints take an identifier you already hold, an email address or a LinkedIn URL, and tell you which Tahoe records belong to that person.
How a person works
A person is not a record. It is a lookup result: the identity you resolved, plus pointers to the records that match it. The response says so in a field: "authoritative": false.
Nothing is merged. A person does not have “an email address”; it points at records that each have their own. Read the records themselves when you need their data.
| Identifier | Matches | Confidence |
|---|---|---|
linkedin_url | Applicants, sourced profiles and shared-pool profiles with that LinkedIn profile | exact |
email | Applicants who gave that email address | probable |
A LinkedIn URL identifies one person, so a match on it is exact. An email match is only probable, because shared, reused and role addresses exist. Each kind of record is only looked at if your key can read it: applicants need applicants:read, sourced profiles sourced_profiles:read and pool profiles pool:read. Records your key cannot read are left out and not counted.
GET/people/resolvepeople:resolve
The entry point. Send exactly one of email or linkedin_url as a query parameter. Sending neither or both is 400 invalid_request. URL-encode the value.
curl -G https://tahoe.workonward.com/api/partner/v1/people/resolve \
-H "Authorization: Bearer $TAHOE_API_KEY" \
--data-urlencode "linkedin_url=https://www.linkedin.com/in/priya-raman-9x8y7z"{
"object": "person",
"id": "per_Lk7d2QmXr9Tz4Wc8",
"canonical_id": "per_Lk7d2QmXr9Tz4Wc8",
"aliases": ["per_Em3x8Rk2Wq5Nb7Jd"],
"identity": { "kind": "linkedin_url", "value": "linkedin.com/in/priya-raman-9x8y7z" },
"authoritative": false,
"display": {
"full_name": "Priya Raman",
"headline": "Field Operations Manager at Northwind Logistics",
"location": "Columbus, Ohio, United States",
"photo_url": null
},
"display_source": {
"object": "sourced_profile",
"id": "cnd_8Fj3kLm2Qd7s",
"workspace_id": "wsp_4Kd8sPm2Qx7L"
},
"profiles": [
{
"object": "sourced_profile",
"id": "cnd_8Fj3kLm2Qd7s",
"workspace_id": "wsp_4Kd8sPm2Qx7L",
"match": { "on": "linkedin_url", "confidence": "exact", "strength": "full" },
"provenance": { "origin": "sourced", "provider": "xray" }
},
{
"object": "applicant",
"id": "apl_2Xj7kQd4Rm8s",
"workspace_id": "wsp_4Kd8sPm2Qx7L",
"match": { "on": "linkedin_url", "confidence": "exact", "strength": "full" },
"provenance": { "origin": "applicant", "provider": null }
}
],
"counts": {
"profiles": 2,
"sourced_profiles": 1,
"applicants": 1,
"pool_profiles": 0,
"exact_matches": 2
},
"links": {
"self": "/api/partner/v1/people/per_Lk7d2QmXr9Tz4Wc8",
"profiles": "/api/partner/v1/people/per_Lk7d2QmXr9Tz4Wc8/profiles",
"contact_info": "/api/partner/v1/people/per_Lk7d2QmXr9Tz4Wc8/contact-info",
"resumes": "/api/partner/v1/people/per_Lk7d2QmXr9Tz4Wc8/resumes",
"applications": "/api/partner/v1/people/per_Lk7d2QmXr9Tz4Wc8/applications"
}
}| Field | Meaning |
|---|---|
id | The person handle for the identifier you sent. |
canonical_id | The handle for the strongest identifier known for this person (a LinkedIn URL beats an email). Store this one, not id. |
aliases | The other person handles that lead to the same human. |
identity | The identifier you resolved on, as Tahoe normalized it. |
authoritative | Always false: a reminder that this is a lookup, not a record. |
display, display_source | A name, headline, location and photo for showing in a UI, and which record they came from. |
profiles | Pointers to the matching records: type, handle, workspace, how it matched and where it came from. No values. |
counts.exact_matches | How many pointers matched exactly. If it is 0, every match is only probable. |
A shared role address such as [email protected] or [email protected] is refused with 400 not_an_identity: keying a person on it would merge everyone who uses that inbox. A valid personal address that is not in Tahoe returns 404.
POST/people/resolve:batchpeople:resolve
Up to 100 identifiers in one call, for matching your own table against Tahoe. Each key is an object with a kind (email or linkedin_url) and a value. It is in the expensive rate-limit tier (60 per minute).
curl -X POST https://tahoe.workonward.com/api/partner/v1/people/resolve:batch \
-H "Authorization: Bearer $TAHOE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"keys": [
{ "kind": "linkedin_url", "value": "https://www.linkedin.com/in/priya-raman-9x8y7z" },
{ "kind": "email", "value": "[email protected]" },
{ "kind": "email", "value": "[email protected]" },
{ "kind": "email", "value": "[email protected]" }
]
}'{
"object": "list",
"data": [
{
"object": "resolution",
"input": { "kind": "linkedin_url", "value": "https://www.linkedin.com/in/priya-raman-9x8y7z" },
"matched": true,
"person": {
"object": "person",
"id": "per_Lk7d2QmXr9Tz4Wc8",
"canonical_id": "per_Lk7d2QmXr9Tz4Wc8",
"counts": { "profiles": 2, "exact_matches": 2 }
}
},
{
"object": "resolution",
"input": { "kind": "email", "value": "[email protected]" },
"matched": true,
"person": {
"object": "person",
"id": "per_Qw4n8Tz2Lk6Hs1Vb",
"canonical_id": "per_Rt5m9Xc3Jp7Gd2Nf",
"counts": { "profiles": 1, "exact_matches": 0 }
}
},
{
"object": "resolution",
"input": { "kind": "email", "value": "[email protected]" },
"matched": false,
"reason": "no_match"
},
{
"object": "resolution",
"input": { "kind": "email", "value": "[email protected]" },
"matched": false,
"reason": "not_an_identity"
}
],
"has_more": false,
"next_cursor": null
}It is a POST only so that a hundred email addresses stay out of URLs, where they would end up in access logs. It changes nothing and is safe to repeat.
GET/people/{person_handle}people:resolve
Looks a person up again from a handle you stored, and returns the same object as GET /people/resolve. If canonical_id differs from id, read the canonical one too: its matches may be exact where the original’s were only probable.
GET/people/{person_handle}/profilespeople:resolve
Just the pointers, as a list. Use it when you have a person and want to know which records to read.
GET/people/{person_handle}/contact-infocontact:read
Contact details from every exactly matched applicant and sourced profile behind this person, each value labeled with the record it came from, and duplicates removed. Phone numbers also need contact:phone:read. From a sourced profile, only details the workspace has already revealed are returned. Each value counts against the daily personal-data budget.
Probable matches add nothing here; the response counts them in excluded_probable_matches. Because email only ever matches probably, a person you resolved by email alone gets no values. This example is the email-resolved person from the batch above:
curl https://tahoe.workonward.com/api/partner/v1/people/per_Qw4n8Tz2Lk6Hs1Vb/contact-info \
-H "Authorization: Bearer $TAHOE_API_KEY"{
"object": "contact_info",
"subject": { "object": "person", "id": "per_Qw4n8Tz2Lk6Hs1Vb" },
"emails": [],
"phones": [],
"unsubscribed": false,
"excluded_probable_matches": 1,
"excluded_reason": "Only exact identity matches contribute contact values. Probable matches are listed under /profiles so you can judge them yourself."
}GET/people/{person_handle}/resumesresume:read
Every resume from the exactly matched applicants behind this person, each with its own resume_access block.
GET/people/{person_handle}/applicationsapplications:read
Every application from the exactly matched applicants behind this person. Resolve a LinkedIn URL and call this to answer “has this candidate applied to us before?”
Related events
person.identity_linked and person.canonical_id_changed, both under people:resolve. See the change feed.