Partner API

Build on Tahoe

A read-only HTTP API over the jobs, applications, candidates and pipelines in a Tahoe workspace, a change feed and webhooks to stay current, and Sign in with Tahoe so your users can authenticate with the account they already have.

Base URL and version

One base URL for every environment. There is no separate sandbox host — a test key and a live key read the same workspace through the same path, and the prefix on the secret is what tells them apart in a log.

Base URL
https://tahoe.workonward.com/api/partner/v1

Every response carries the API version in Tahoe-Partner-Api-Version, and GET /ping reports it without authentication. The version is a date, and it changes only for a breaking change — new fields, new endpoints and new event types are added without one.

Three things to know before you start

It is read-only, and that is a design decision

Every data endpoint is a GET. The two POSTs that exist — /people/resolve:batch and /pool/search — are reads with bodies too large for a query string. Nothing you can call through this API creates, edits or deletes anything in a Tahoe workspace.

So a Tahoe integration cannot corrupt a customer’s hiring data, and a leaked key is a confidentiality incident rather than an integrity one. Write access, if it ever exists, will be a separate surface with its own scopes and its own review.

Personal data is metered and audited, not merely scoped

Emails, phone numbers and resumes sit behind their own scopes, and reading one writes a row naming the key, the record and the field. There is a daily budget on personal-data reads that is separate from the request rate limit: you can page jobs all day and still be stopped from harvesting phone numbers.

The API also never sells you data a workspace has not already paid to reveal. A contact field that has never been unlocked is reported as withheld, with a reason — it is not silently null, and it is not available at a cheaper price here than in the product.

Nothing is exposed that a recruiter could not already see

The API is a view onto one workspace’s own data. It is not a back door into Tahoe’s whole corpus, another tenant’s pipeline, or the content of a phone screening. What is never exposed lists the categories that have no representation in this API at all.

The surface, at a glance

AreaWhat you getScopes
JobsRequisitions, structured sections, application-form schema, pipeline stages, pre-screening questionsjobs:read
ApplicationsApplications, stage and status, form answers, match scores, stage history, resume metadata and downloadsapplications:read
ApplicantsThe people who applied, their applications, their resumes, their contact informationapplicants:read
Sourced profilesCandidates the workspace sourced, revealed contact information, attachments, pipeline membershipsourced_profiles:read
Shared poolTahoe's pool of public professional profiles, and a search over it that spends nothingpool:read
PeopleResolve your own records to a Tahoe person by email or LinkedIn URL, then read across every profile behind thempeople:resolve
Projects and listsProjects, candidate lists, members and their stagelists:read
AnalyticsAggregate hiring-funnel countsanalytics:read
EventsA cursored change feed, erasure notices, and webhook delivery statusevents:read
Sign in with TahoeStandard OIDC authorization-code flow with PKCEno API key

Who can create a key

API keys are minted from Settings → Developer inside Tahoe, and only by users whose account email is on the workonward.com, workonward.org or dhdgroup.net domains. Everyone else sees the tab explain why it is unavailable rather than a button that fails.

Your first call

GET /me reports exactly what the credential in your hand can do — its scopes, which workspaces it can reach, whether it must name one, and its current limits. It is the fastest way to tell a configuration problem from a code problem.

Request
curl https://tahoe.workonward.com/api/partner/v1/me \
  -H "Authorization: Bearer $TAHOE_API_KEY"
Response
{
  "object": "credential",
  "id": "pk_9tRc4mQx7Lb2",
  "name": "WorkOnward HRIS sync",
  "environment": "live",
  "scopes": ["applications:read", "jobs:read", "workspaces:read"],
  "scope_descriptions": {
    "applications:read": "Applications, their stage and status, and match scores.",
    "jobs:read": "Job postings, their content, application forms and pipeline stages.",
    "workspaces:read": "Workspace name and creation date."
  },
  "workspace_scope": "single",
  "workspace_ids": ["wsp_4Kd8sPm2Qx7L"],
  "workspace_id_required": false,
  "rate_limits": {
    "general_per_minute": 600,
    "expensive_per_minute": 60,
    "download_per_minute": 30,
    "personal_data_reads_per_day": 5000,
    "personal_data_reads_used_today": 128,
    "max_page_size": 100,
    "default_page_size": 25,
    "max_result_window": 10000
  },
  "created_at": "2026-09-09T10:14:22.510Z",
  "expires_at": "2026-12-08T10:14:22.510Z",
  "api_version": "2026-09-09"
}

Getting help

Every error response carries a request_id. Quote it — with it a support request is a lookup, and without it, a guess.