Skip to content

Quickstart

Make your first API call in a few minutes.

This page takes you from no key to a complete read of your published jobs, and then shows how to keep your copy current without reading everything again. You need curl for the first steps and Python for the paging example. There is no SDK to install: the API is plain HTTPS and JSON.

Create a key

In Tahoe, open Settings, choose Developer, then Create key. You need to be an owner or admin of the workspace. Choose:

  • the scopes it needs (pick the narrowest set that does the job);
  • whether it is a test or a live key, and a name for it, such as “Acme HRIS sync”. Name the key after the system that will hold it, not after a person;
  • how long it lasts, and optionally the IP addresses or ranges it may be used from.

The key reads the workspace you created it in. If Settings has no Developer tab, email [email protected].

Check what the key can do

Before you write any integration code, ask the API about the key itself. GET /me turns “why do my calls fail?” into a one-line answer.

Request
export TAHOE_API_KEY="thk_live_..."

curl -s https://tahoe.workonward.com/api/partner/v1/me \
  -H "Authorization: Bearer $TAHOE_API_KEY"
Response (abridged)
{
  "object": "credential",
  "id": "pk_9tRc4mQx7Lb2",
  "name": "Acme HRIS sync",
  "environment": "live",
  "scopes": ["applications:read", "events:read", "jobs:read"],
  "workspace_scope": "list",
  "workspace_ids": ["wsp_4Kd8sPm2Qx7L"],
  "workspace_id_required": false,
  "expires_at": "2026-12-08T10:14:22.510Z",
  "api_version": "2026-09-09"
}

Read three fields:

  • scopes: what the key may read.
  • workspace_ids: the workspaces it can reach.
  • workspace_id_required: whether every call must name a workspace with ?workspace_id=. Most keys belong to one workspace, and this is false.

Read a page of jobs

Lists come back one page at a time. Ask for a page, then follow next_cursor until it is null.

Request
curl -s "https://tahoe.workonward.com/api/partner/v1/jobs?status=published&limit=25" \
  -H "Authorization: Bearer $TAHOE_API_KEY"
Response (abridged)
{
  "object": "list",
  "data": [
    {
      "object": "job",
      "id": "job_7Kd2mXq4Rp8v",
      "workspace_id": "wsp_4Kd8sPm2Qx7L",
      "slug": "field-coordinator-a41f",
      "status": "published",
      "title": "Field Coordinator",
      "department": "Operations",
      "employment_type": "full_time",
      "location_type": "onsite",
      "locations": ["Columbus, Ohio"],
      "experience_level": "mid",
      "years_min": 2,
      "years_max": 5,
      "accept_applications": true,
      "summary": "Run day-to-day site schedules for our Ohio crews.",
      "source": {
        "system": "tahoe_native",
        "external_id": null,
        "company_name": null,
        "company_logo_url": null,
        "canonical_url": null
      },
      "published_at": "2026-08-14T09:02:11.004Z",
      "created_at": "2026-08-12T15:41:07.882Z",
      "updated_at": "2026-09-08T11:20:45.331Z",
      "links": { "self": "/api/partner/v1/jobs/job_7Kd2mXq4Rp8v" }
    }
  ],
  "has_more": true,
  "next_cursor": "cur_eyJrIjoiMjAyNi0wOC0xNFQwOTowMjoxMVoiLCJpIjoiam9iXzdLZDJt..."
}

Page all the way through

The loop below reads every published job. It works for any list endpoint: change the path and the filters.

Python
import os
import requests

BASE = "https://tahoe.workonward.com/api/partner/v1"
SESSION = requests.Session()
SESSION.headers["Authorization"] = f"Bearer {os.environ['TAHOE_API_KEY']}"


def page_all(path, **filters):
    """Yield every row from a list endpoint, following next_cursor."""
    cursor = None
    while True:
        # Send the same filters on every page, plus the cursor. A cursor is
        # bound to the filters it came from, so changing one mid-loop is
        # refused with invalid_cursor.
        params = dict(filters)
        if cursor:
            params["cursor"] = cursor
        response = SESSION.get(f"{BASE}{path}", params=params, timeout=30)
        response.raise_for_status()
        body = response.json()

        yield from body["data"]

        cursor = body.get("next_cursor")
        if not cursor:
            return


for job in page_all("/jobs", status="published", limit=100):
    print(job["id"], job["title"])

Send the same filters with every page. You may change limit between pages, but a changed filter makes the cursor invalid (400 invalid_cursor). Cursors also expire after an hour, so finish a pass rather than pausing halfway.

Keep up with changes

Once you have a full copy, do not read the whole workspace again on a schedule. Read the change feed instead. It lists every change in order, oldest first.

Request
curl -s "https://tahoe.workonward.com/api/partner/v1/events?limit=100&type=job.published,job.updated,job.closed" \
  -H "Authorization: Bearer $TAHOE_API_KEY"

Each event has a sequence number. Save the sequence of the last event you processed, and next time start after it:

Request
curl -s "https://tahoe.workonward.com/api/partner/v1/events?after=48213&limit=100&type=job.published,job.updated,job.closed" \
  -H "Authorization: Bearer $TAHOE_API_KEY"

The sequence number is your sync position. A timestamp is not: changes are written at the same time by different processes, so their times can arrive slightly out of order. The key needs the events:read scope for this, plus the scope of each resource you want to hear about.

Next steps