Skip to content

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.

IdentifierMatchesConfidence
linkedin_urlApplicants, sourced profiles and shared-pool profiles with that LinkedIn profileexact
emailApplicants who gave that email addressprobable

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.

Request
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"
Response
{
  "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"
  }
}
FieldMeaning
idThe person handle for the identifier you sent.
canonical_idThe handle for the strongest identifier known for this person (a LinkedIn URL beats an email). Store this one, not id.
aliasesThe other person handles that lead to the same human.
identityThe identifier you resolved on, as Tahoe normalized it.
authoritativeAlways false: a reminder that this is a lookup, not a record.
display, display_sourceA name, headline, location and photo for showing in a UI, and which record they came from.
profilesPointers to the matching records: type, handle, workspace, how it matched and where it came from. No values.
counts.exact_matchesHow 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).

Request
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]" }
    ]
  }'
Response (person objects shortened)
{
  "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:

Request
curl https://tahoe.workonward.com/api/partner/v1/people/per_Qw4n8Tz2Lk6Hs1Vb/contact-info \
  -H "Authorization: Bearer $TAHOE_API_KEY"
Response
{
  "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?”

person.identity_linked and person.canonical_id_changed, both under people:resolve. See the change feed.