API reference

Applicants

A person who applied. This is the cleanest population in Tahoe from a consent point of view — they came to your customer on purpose — and the endpoints say so explicitly.

GET/applicantsapplicants:read

Applicants in the workspace, newest first.

ParameterTypeNotes
qstringSearch by name.
updated_afterRFC 3339For incremental sync.
limitintegerDefault 25, max 100.
cursorstringFrom the previous page.
Response
{
  "object": "list",
  "data": [
    {
      "object": "applicant",
      "id": "apl_5Nx3jLm7Qd2s",
      "workspace_id": "wsp_4Kd8sPm2Qx7L",
      "full_name": "Priya Raman",
      "location": "Seoul, KR",
      "linkedin_url": "https://www.linkedin.com/in/priya-raman-1a2b3c",
      "github_url": null,
      "portfolio_url": null,
      "created_at": "2026-09-02T14:21:09.115Z",
      "updated_at": "2026-09-07T09:44:31.002Z",
      "provenance": {
        "origin": "applicant",
        "acquisition": "inbound_application",
        "provider": null,
        "owner_workspace_id": "wsp_4Kd8sPm2Qx7L",
        "first_seen_at": "2026-09-02T14:21:09.115Z",
        "last_seen_at": "2026-09-07T09:44:31.002Z",
        "consent": {
          "basis": "candidate_submitted",
          "candidate_facing_notice": true,
          "unsubscribed": false,
          "suppressed": false
        },
        "licence": {
          "redistributable": false,
          "note": "Submitted to one employer for one role. Not licensed for onward redistribution."
        }
      },
      "external_refs": [],
      "links": {
        "self": "/api/partner/v1/applicants/apl_5Nx3jLm7Qd2s",
        "applications": "/api/partner/v1/applicants/apl_5Nx3jLm7Qd2s/applications"
      }
    }
  ],
  "has_more": true,
  "next_cursor": "cur_eyJrIjoiMjAyNi0wOS0wMlQxNDoyMTowOVoi..."
}

The provenance block

Every person-shaped resource in this API carries one, and it is not decoration — it is the field your compliance team keys on.

FieldMeaning
originWhich population this person came from: applicant, sourced, shared_pool or ats_import.
acquisitionHow they arrived — e.g. inbound_application, provider_search.
consent.basiscandidate_submitted, legitimate_interest_sourcing, public_profile or customer_provided.
consent.candidate_facing_noticeWhether this person saw a notice at the moment their data was collected. True only for applicants.
consent.unsubscribedThey asked not to be contacted. Honour it regardless of scope.
consent.suppressedThey are under an erasure or suppression notice.
licence.redistributableComputed, and always false.

external_refs

When the applicant arrived through an ATS connection, this carries the identifier in that system — exactly what an HRIS needs to dedupe against its own records without guessing on names.

An applicant imported from Greenhouse
"external_refs": [
  {
    "system": "greenhouse",
    "type": "candidate",
    "id": "48213097",
    "connection_id": "ci_7Kd2mXq4Rp8v",
    "link_origin": "import",
    "last_synced_at": "2026-09-07T03:00:11.408Z"
  }
]

Match on system plus id. connection_id identifies your customer’s ATS connection rather than anything you should address, and an applicant created natively in Tahoe has an empty array.

GET/applicants/{applicant_handle}applicants:read

One applicant, same shape as a list row.

GET/applicants/{applicant_handle}/applicationsapplications:read

Every application this person has submitted in the workspace — the same objects as the applications endpoints. Note the scope: it is applications:read, not applicants:read, because the data returned is applications.

GET/applicants/{applicant_handle}/resumesresume:read

Every resume this person has submitted, across all their applications. Each row carries its own resume_access, so the same person can have one readable resume and one locked one.

GET/applicants/{applicant_handle}/contact-infocontact:read

Email addresses and, with contact:phone:read as well, phone numbers.

Response
{
  "object": "contact_info",
  "subject": { "object": "applicant", "id": "apl_5Nx3jLm7Qd2s" },
  "emails": [
    { "value": "[email protected]", "kind": "submitted", "status": "found" }
  ],
  "phones": [],
  "unsubscribed": false,
  "field_states": {
    "work_email": "found",
    "personal_email": "not_found",
    "phone": "found"
  },
  "restricted": ["phones"],
  "restricted_reason": {
    "phones": "scope_required:contact:phone:read"
  }
}

field_states is how you tell the two failure modes apart: not_found means Tahoe does not have that field, while a field named in restricted means Tahoe has it and you were not granted it. Storing the second as “no phone number” is the mistake the restricted contract exists to prevent.

An applicant’s contact details are candidate-submitted, so they are never paywalled — they gave them to your customer for this purpose. They are still personal data: each disclosed value consumes one unit of the daily budget and writes an audit row.

Related events

applicant.created, applicant.updated, applicant.deleted and applicant.unsubscribed, all under applicants:read. applicant.unsubscribed is the one to wire first — it is the event that stops you emailing someone who asked you not to.