프로젝트와 리스트
프로젝트, 리스트, 후보자별 진행 위치, 그리고 리스트 만들고 채우기.
리스트는 소싱 쪽의 파이프라인입니다. 프로젝트는 리스트를 묶고, 리스트에는 소싱한 후보자가 들어 있으며, 멤버십은 각 후보자가 어느 단계에 있는지 기록합니다. 다섯 엔드포인트가 이 정보를 읽으며 lists:read가 필요합니다. 세 가지 호출이 더 있어 리스트를 만들고, 채우고, 후보자의 단계를 정합니다. 이쪽은 lists:write가 필요합니다.
세 가지의 관계
프로젝트는 하나의 채용 업무 단위입니다. 프로젝트 안에 리스트가 있고, 각 리스트에는 멤버십이 있습니다. 멤버십은 소싱한 프로필을 가리키며 그 리스트에서의 단계를 알려 줍니다.
이 구조는 지원서 파이프라인과 나란히 있지만 별개입니다. 지원자는 공고의 파이프라인 단계를 거치고, 소싱한 후보자는 리스트의 단계를 거칩니다. 둘을 하나의 퍼널로 합치려 하지 마세요. 채용 과정의 서로 다른 지점에 있는 서로 다른 사람들입니다.
GET/projectslists:read
워크스페이스의 프로젝트를 최근 수정된 순서로 보여 줍니다.
curl https://tahoe.workonward.com/api/partner/v1/projects \
-H "Authorization: Bearer $TAHOE_API_KEY"{
"object": "list",
"data": [
{
"object": "project",
"id": "prj_7Kd2mXq4Rp8v",
"workspace_id": "wsp_4Kd8sPm2Qx7L",
"name": "Ohio field operations hiring",
"description": "Field and site roles for the Columbus and Dayton depots.",
"created_at": "2026-07-01T09:00:00.000Z",
"updated_at": "2026-09-06T12:08:55.400Z",
"links": {
"self": "/api/partner/v1/projects/prj_7Kd2mXq4Rp8v",
"lists": "/api/partner/v1/projects/prj_7Kd2mXq4Rp8v/lists"
}
}
],
"has_more": false,
"next_cursor": null
}GET/projects/{project_handle}/listslists:read
프로젝트 하나에 속한 리스트를 아래 행과 같은 형태로 보여 줍니다.
GET/listslists:read
워크스페이스의 모든 후보자 리스트로, 프로젝트에 속하지 않은 리스트도 포함합니다. 그런 리스트는 project_id: null입니다.
{
"object": "list",
"data": [
{
"object": "list",
"id": "lst_3Rp8vKd2mXq4",
"workspace_id": "wsp_4Kd8sPm2Qx7L",
"project_id": "prj_7Kd2mXq4Rp8v",
"name": "Field Coordinators, Columbus",
"description": null,
"created_at": "2026-07-02T11:20:00.000Z",
"updated_at": "2026-09-06T12:08:55.400Z",
"links": {
"self": "/api/partner/v1/lists/lst_3Rp8vKd2mXq4",
"members": "/api/partner/v1/lists/lst_3Rp8vKd2mXq4/members"
}
}
],
"has_more": false,
"next_cursor": null
}각 행의 object는 "list"이며, 행들을 감싸는 봉투의 object도 같은 값입니다. 바깥쪽이 봉투이고, 안쪽의 행이 후보자 리스트입니다.
GET/lists/{list_handle}lists:read
위의 행과 같은 형태의 리스트 하나입니다.
GET/lists/{list_handle}/memberslists:read
리스트에 누가 있고 어느 단계에 있는지 보여 줍니다. 멤버십은 소싱한 프로필을 담지 않고 가리키기만 합니다. 인물의 세부 정보는 프로필을 읽으세요.
curl "https://tahoe.workonward.com/api/partner/v1/lists/lst_3Rp8vKd2mXq4/members?limit=100" \
-H "Authorization: Bearer $TAHOE_API_KEY"{
"object": "list",
"data": [
{
"object": "list_membership",
"id": "mem_9Xj4kQd7Rm2s",
"list_id": "lst_3Rp8vKd2mXq4",
"sourced_profile_id": "cnd_8Fj3kLm2Qd7s",
"stage": "contacted",
"stage_updated_at": "2026-09-04T08:12:44.201Z",
"added_at": "2026-08-19T07:41:02.115Z"
}
],
"has_more": false,
"next_cursor": null
}stage_updated_at을 쓰면 따로 이력을 보관하지 않고도 단계별 소요 시간을 계산할 수 있고, added_at은 후보자가 리스트에 추가된 시점입니다. 반대로 후보자 한 명이 들어 있는 모든 리스트를 보려면 프로필의 리스트 멤버십을 쓰세요.
리스트 만들고 채우기
POST 호출 세 가지가 리스트를 바꿉니다. Tahoe 제품이 보여 주는 것과 같은 레코드를 바꾸므로, 연동 프로그램이 채운 리스트는 리크루터가 채운 리스트와 똑같이 보입니다. 멤버, 멤버 수, 단계 열이 같습니다. 세 호출 모두 lists:write와 Tahoe 사용자가 만든 API 키가 필요합니다. Sign in with Tahoe 토큰은 403 write_requires_api_key를 받습니다. 연결된 앱의 쓰기가 켜져 있으면 예외입니다. 일반 속도 제한 등급을 쓰고, 선택 사항인 Idempotency-Key를 받으며, origin이 들어 있는 이벤트를 보냅니다.
여러 워크스페이스에 접근하는 키는 읽기와 마찬가지로 ?workspace_id=wsp_...로 하나를 지정합니다. 형식이 잘못되었거나, 종류가 다르거나, 다른 워크스페이스에 속한 핸들은 항상 404 not_found이며 403이 아닙니다. 이 호출들은 후보자에게 아무것도 보내지 않고 아무것도 삭제하지 않습니다. 멤버를 제거하는 호출은 없습니다.
POST/listslists:write
빈 리스트를 만들고, 201과 함께 GET /lists/{list_handle}와 같은 형태의 리스트를 반환합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
name | string, 필수 | 1~120자. 앞뒤 공백은 잘립니다. |
description | string | 선택. 최대 500자. |
project_id | 핸들 | 선택. 이 워크스페이스의 프로젝트여야 하며, 아니면 404 not_found입니다. |
curl -X POST https://tahoe.workonward.com/api/partner/v1/lists \
-H "Authorization: Bearer $TAHOE_API_KEY" \
-H "Idempotency-Key: create-list-platform-0001" \
-H "Content-Type: application/json" \
-d '{
"project_id": "prj_5Nx3jLm7Qd2s",
"name": "Platform shortlist",
"description": "Q4 backend hires"
}'{
"object": "list",
"id": "lst_3Rp8vKd2mXq4",
"workspace_id": "wsp_4Kd8sPm2Qx7L",
"project_id": "prj_5Nx3jLm7Qd2s",
"name": "Platform shortlist",
"description": "Q4 backend hires",
"created_at": "2026-10-08T12:00:00Z",
"updated_at": "2026-10-08T12:00:00Z",
"links": {
"self": "/api/partner/v1/lists/lst_3Rp8vKd2mXq4",
"members": "/api/partner/v1/lists/lst_3Rp8vKd2mXq4/members"
}
}project_id를 생략하면 리스트는 워크스페이스에서 가장 오래된 프로젝트에 들어갑니다. 워크스페이스에 프로젝트가 없으면 Tahoe가 “Imported”라는 프로젝트를 먼저 만듭니다. 제품에서 리스트를 고르지 않고 연락처를 저장할 때와 같은 동작입니다.- 이름은 중복을 걸러 내지 않습니다. 같은 이름으로 요청을 두 번 보내면 제품과 마찬가지로 리스트가 두 개 만들어집니다. 재시도 때문에 두 번째 리스트가 생기지 않도록
Idempotency-Key를 보내세요. description은 저장되고 읽기 엔드포인트가 반환합니다. Tahoe 리스트 화면에는 아직 표시되지 않습니다.- 새 리스트에는 멤버가 없고
candidate_count는 0입니다.list.created이벤트를 보냅니다.
POST/lists/{list_handle}/memberslists:write
소싱한 프로필을 최대 50개까지 리스트에 추가합니다. 배치는 항상 끝까지 처리되며 200을 반환합니다. 항목별 결과는 본문에 있습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
members | 배열, 필수 | 1~50개 항목. 각 항목에는 sourced_profile_id와 applicant_id 중 정확히 하나만 있어야 합니다. 항목은 핸들로만 식별하며, 이름이나 이메일로는 식별하지 않습니다. |
curl -X POST https://tahoe.workonward.com/api/partner/v1/lists/lst_3Rp8vKd2mXq4/members \
-H "Authorization: Bearer $TAHOE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"members": [
{ "sourced_profile_id": "cnd_8Lp3wNc5Tz1k" },
{ "sourced_profile_id": "cnd_2Wq7vHd4Rm9x" },
{ "applicant_id": "apl_5Nx3jLm7Qd2s" }
]
}'{
"object": "list_member_batch",
"list_id": "lst_3Rp8vKd2mXq4",
"requested": 3,
"added": 1,
"already_in_list": 1,
"not_found": 0,
"unsupported": 1,
"candidate_count": 12,
"results": [
{
"index": 0,
"status": "added",
"sourced_profile_id": "cnd_8Lp3wNc5Tz1k",
"applicant_id": null,
"membership": {
"object": "list_membership",
"id": "mem_6Qm2xKd4Rp8v",
"list_id": "lst_3Rp8vKd2mXq4",
"sourced_profile_id": "cnd_8Lp3wNc5Tz1k",
"stage": null,
"stage_updated_at": null,
"added_at": "2026-10-08T12:00:00Z"
}
},
{
"index": 1,
"status": "already_in_list",
"sourced_profile_id": "cnd_2Wq7vHd4Rm9x",
"applicant_id": null,
"membership": { "object": "list_membership", "id": "mem_9Hn4tWc7Lv2d", "...": "..." }
},
{
"index": 2,
"status": "unsupported",
"sourced_profile_id": null,
"applicant_id": "apl_5Nx3jLm7Qd2s",
"membership": null
}
]
}results에는 요청한 항목마다 하나씩, 요청 순서대로 들어 있습니다. 맨 위의 개수를 모두 더하면 requested가 됩니다.
| status | 의미 |
|---|---|
added | 새 멤버십이 만들어졌습니다. |
already_in_list | 프로필이 이미 이 리스트에 있었습니다. 바뀐 것은 없으며 오류가 아닙니다. |
not_found | 이 워크스페이스의 소싱한 프로필 핸들이 아닙니다. 형식이 잘못되었거나, 다른 워크스페이스의 것이거나, 표시할 데이터가 없는 레코드입니다. 다른 항목에는 영향이 없으며, 응답은 이 셋 중 무엇인지 알려 주지 않습니다. |
unsupported | applicant_id를 보냈습니다. 리스트에는 소싱한 프로필이 들어가며 지원자는 해당하지 않습니다. 나중에 쓸 수 있도록 스키마는 이 필드를 받아 주고, 항목은 조회하지 않습니다. |
membership은added와already_in_list에 있습니다. 그id를 단계 호출에 씁니다.- 이 워크스페이스가 이미 가진 프로필만 추가할 수 있습니다. 프로필을 새로 만들 수 없으며, 다른 워크스페이스의 데이터를 연결하는 일도 없습니다.
candidate_count는 쓰기 이후의 멤버 수이며, 아무것도 추가되지 않았다면null입니다. 멤버 수와updated_at은 무언가 추가된 경우에만 요청당 한 번 다시 계산합니다.- 같은 프로필을 한 요청에 두 번 보내면 한 번만 추가됩니다.
added다음에already_in_list입니다. - 리크루터는 새 멤버를 바로 봅니다. 보강 작업은 시작되지 않고 크레딧도 쓰지 않습니다.
- 새 멤버마다
list_membership.added를, 무언가 추가되었다면 그 뒤에list.updated를 한 번 보냅니다. 키 없이 요청을 반복해도 안전합니다. 이미 있는 멤버를 추가하는 것은 아무 일도 하지 않기 때문입니다.
POST/lists/{list_handle}/members/{member_handle}/stagelists:write
멤버 한 명의 리크루터 단계를 정하고, 200과 함께 읽기 엔드포인트가 반환하는 것과 같은 멤버십을 반환합니다. {member_handle}는 멤버 조회나 추가 응답에 있는 mem_ 핸들입니다.
| 필드 | 타입 | 설명 |
|---|---|---|
stage | string, 필수 | 리스트가 가질 수 있는 키이며 1~40자입니다. 정확히 일치해야 하고 대소문자를 구분합니다. Hired는 hired가 아닙니다. |
리스트가 가질 수 있는 키는 다음과 같습니다.
requested,connected,pending,declined: Tahoe 리스트 표의 기본 단계입니다.interviewing,hired: 면접과 채용 분석이 지금도 집계하는 이전 표시입니다.none: 단계를 지웁니다. 멤버의stage가null이 됩니다.- 같은 리스트의 어떤 멤버에게 리크루터가 이미 붙인 사용자 지정 라벨.
curl -X POST https://tahoe.workonward.com/api/partner/v1/lists/lst_3Rp8vKd2mXq4/members/mem_6Qm2xKd4Rp8v/stage \
-H "Authorization: Bearer $TAHOE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "stage": "interviewing" }'{
"object": "list_membership",
"id": "mem_6Qm2xKd4Rp8v",
"list_id": "lst_3Rp8vKd2mXq4",
"sourced_profile_id": "cnd_8Lp3wNc5Tz1k",
"stage": "interviewing",
"stage_updated_at": "2026-10-08T12:05:00Z",
"added_at": "2026-10-08T12:00:00Z"
}param: "stage"가 있는400 invalid_stage는 이 리스트가 가질 수 없는 키라는 뜻입니다. 메시지에 유효한 키가 나열되며, 거부된 값은 그대로 되풀이하지 않습니다.- API로는 새 사용자 지정 라벨을 만들 수 없습니다. 라벨은 리크루터가 제품에서 만들고, 키는 리스트에 이미 있는 라벨만 다시 쓸 수 있습니다. 그래서 연동 프로그램의 자유 텍스트가 리크루터 앞에 예고 없이 나타나지 않습니다.
- 단계는 워크스페이스 전체에 보입니다. 면접과 채용 분석은 제품에서 같은 표시를 했을 때와 똑같이
interviewing과hired를 집계합니다. - 멤버가 이미 가진 단계를 다시 정하면 성공하고, 제품과 마찬가지로
stage_updated_at을 갱신하며 이벤트는 보내지 않습니다. 그 밖의 경우에는 새state와 이전state를 담아list_membership.stage_changed를 보냅니다. - 두 쓰는 쪽이 같은 순간에 단계를 정하면 제품과 마찬가지로 마지막 쓰기가 이깁니다. 제품에 단계용 확인 장치가 없으므로
expected_updated_at도 없습니다.
이 호출들의 오류
| 상태 | 코드 | 조치 |
|---|---|---|
| 400 | invalid_request | 본문 검증에 실패했습니다. errors 목록에 필드가 하나씩 표시됩니다. |
| 400 | invalid_stage | 단계 호출에서만 발생합니다. 메시지에 있는 키 중 하나를 쓰세요. |
| 400 | invalid_idempotency_key | Idempotency-Key 헤더가 허용된 문자 8~255자가 아닙니다. |
| 403 | insufficient_scope | 키에 lists:write가 없습니다. |
| 403 | write_requires_api_key | Sign in with Tahoe 토큰이거나, Tahoe 사용자와 연결되지 않은 자격 증명입니다. |
| 404 | not_found | 리스트, 멤버, 프로젝트가 이 워크스페이스에 없거나, 멤버가 그 리스트에 없습니다. |
| 409 | idempotency_key_reused | 같은 키를 다른 본문과 함께 썼습니다. |
| 409 | idempotency_in_flight | 같은 키의 첫 요청이 아직 실행 중입니다. 잠시 후 다시 시도하세요. |
| 429 | rate_limit_exceeded | Retry-After만큼 기다리세요. |
관련 이벤트
list.created, list.updated, list.deleted, list_membership.added, list_membership.removed, list_membership.stage_changed가 있으며, 모두 lists:read에 속합니다. list_membership.removed는 삭제 이벤트입니다. 멤버십 사본을 보관한다면 그 행을 지우세요. 변경 피드를 참고하세요.
list_membership.* 이벤트는 행을 자기만의 mem_ 핸들로 가리킵니다. 멤버 조회가 반환하는 것과 같은 핸들이며, list_id와 sourced_profile_id가 함께 옵니다. 쓰기로 발생한 이벤트에는 origin이 들어 있습니다.