채용 공고
채용 공고와 섹션, 단계, 양식을 읽고, 내 시스템의 공고를 보내고, 공고를 게시, 마감, 재개하세요.
채용 공고를 읽는 엔드포인트가 일곱 개, 바꾸는 엔드포인트가 다섯 개 있습니다. 목록은 일부러 가볍게, 단건 조회는 전체 내용을 담도록 만들었습니다. 채용 공고 게시판은 목록으로 미러링하고, 실제로 보여 줄 공고만 전체 내용을 가져오세요.
POST /jobs는 ATS나 채용 사이트처럼 자체 시스템에서 온 공고를 만들거나 수정합니다. jobs:write 스코프가 필요합니다. 라이프사이클 호출 네 가지는 팀이 Tahoe에서 쓴 공고를 포함해 워크스페이스의 모든 공고를 게시, 게시 취소, 마감, 재개합니다. 이쪽은 jobs:manage가 필요합니다.
GET/jobsjobs:read
워크스페이스의 채용 공고를 최근 수정된 순서로 보여 줍니다. 기본적으로 published와 closed 공고만 반환하므로, 이 목록을 미러링하는 게시판에 고객이 작성 중인 초안이 실수로 노출되는 일이 없습니다.
| 파라미터 | 타입 | 설명 |
|---|---|---|
status | string | 쉼표로 구분합니다. 기본값은 published,closed입니다. 다른 상태를 받으려면 include_unpublished=true가 필요합니다. |
include_unpublished | boolean | 초안 등 공개되지 않은 상태도 받겠다고 선택합니다. 이 값 없이 그런 상태를 요청하면 400 unpublished_requires_opt_in입니다. |
department | string | 정확히 일치하는 값만 찾습니다. |
location_type | string | remote, hybrid, onsite 중 하나입니다. |
employment_type | string | full_time, part_time, contract, intern, temp 중 하나입니다. |
q | string | 공고 전체를 대상으로 하는 텍스트 검색입니다. |
updated_after | timestamp | 이 시점 이후 변경된 공고만 반환합니다. 전체를 다시 넘겨 보는 대신 증분 동기화에 쓰세요. |
published_after | timestamp | 이 시점 이후 게시된 공고만 반환합니다. |
limit | integer | 기본값 25, 최대 100. |
cursor | string | 이전 페이지에서 받은 값입니다. 같은 필터를 함께 보내세요. |
curl "https://tahoe.workonward.com/api/partner/v1/jobs?status=published&limit=100" \
-H "Authorization: Bearer $TAHOE_API_KEY"{
"object": "list",
"data": [
{
"object": "job",
"id": "job_7Kd2mXq4Rp8v",
"workspace_id": "wsp_4Kd8sPm2Qx7L",
"slug": "warehouse-shift-lead-a41f9c02",
"status": "published",
"title": "Warehouse Shift Lead",
"department": "Operations",
"team": "Fulfillment",
"employment_type": "full_time",
"location_type": "onsite",
"locations": ["Columbus, Ohio"],
"experience_level": "senior",
"years_min": 4,
"years_max": 8,
"compensation": {
"salary_min": 58000,
"salary_max": 72000,
"currency": "USD",
"interval": "annual",
"unit": "major",
"equity": null,
"commission": false
},
"accept_applications": true,
"external_apply_url": null,
"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",
"close_at": null,
"created_at": "2026-08-12T15:41:07.882Z",
"updated_at": "2026-09-08T11:20:45.331Z",
"links": {
"self": "/api/partner/v1/jobs/job_7Kd2mXq4Rp8v",
"applications": "/api/partner/v1/jobs/job_7Kd2mXq4Rp8v/applications",
"application_form": "/api/partner/v1/jobs/job_7Kd2mXq4Rp8v/application-form",
"prescreen_questions": "/api/partner/v1/jobs/job_7Kd2mXq4Rp8v/prescreen-questions",
"pipeline_stages": "/api/partner/v1/jobs/job_7Kd2mXq4Rp8v/pipeline-stages",
"sections": "/api/partner/v1/jobs/job_7Kd2mXq4Rp8v/sections"
},
"summary": "Run the night shift for a 40-person fulfillment team."
}
],
"has_more": true,
"next_cursor": "cur_eyJrIjoiMjAyNi0wOC0xNFQwOTowMjoxMVoi..."
}목록의 각 행에는 짧은 summary가 있습니다. 설명, 담당 업무, 자격 요건, 기술, 복리후생을 담은 전체 content는 단건 조회에서만 제공합니다. 급여는 센트 같은 보조 단위가 아니라 통화의 기본 단위로 표시하며, "unit": "major"가 이를 나타냅니다. Tahoe에서 만든 공고는 source.system이 tahoe_native입니다.
GET/jobs/{job_handle}jobs:read
summary 대신 전체 content 객체와 application_count를 담은 공고 하나입니다. 아래 응답은 목록 행과 다른 부분만 남기고 줄였습니다.
curl https://tahoe.workonward.com/api/partner/v1/jobs/job_7Kd2mXq4Rp8v \
-H "Authorization: Bearer $TAHOE_API_KEY"{
"object": "job",
"id": "job_7Kd2mXq4Rp8v",
"title": "Warehouse Shift Lead",
"status": "published",
"content": {
"summary": "Run the night shift for a 40-person fulfillment team.",
"description_md": "## About the role\n\nYou will lead the night shift ...",
"responsibilities": ["Plan staffing for each shift", "Run the start-of-shift safety briefing"],
"requirements": ["4+ years in warehouse operations", "2+ years leading a team"],
"nice_to_have": ["Forklift certification"],
"skills_required": ["Team leadership", "Inventory control", "Workplace safety"],
"skills_preferred": ["Lean methods"],
"benefits": ["Health, dental and vision cover", "Paid time off"]
},
"restricted": ["voice_screening.dial_in_code"],
"restricted_reason": {
"voice_screening.dial_in_code": "scope_required:jobs:screening:read"
}
}description_md는 고객이 작성한 Markdown입니다. Markdown으로 렌더링하거나 서식을 제거해 쓰고, 웹 페이지에 넣기 전에는 위험한 HTML을 걸러 내세요(sanitize).
GET/jobs/by-slug/{slug}jobs:read
공개 채용 공고 URL의 슬러그로 공고를 찾아, 단건 조회와 같은 형태로 반환합니다. 후보자가 클릭한 링크만 가지고 있을 때 쓰세요. 슬러그가 아니라 응답으로 받은 job_ 핸들을 저장하세요.
GET/jobs/{job_handle}/sectionsjobs:read
공고 하나에 대한 모든 내용을 한 번에 가져옵니다. 전체 공고와 함께 pipeline_stages, application_form(아래 두 엔드포인트와 같은 객체)이 들어 있습니다. jobs:screening:read가 있으면 prescreen_questions도 포함되고, 없으면 prescreen_questions가 restricted에 표시됩니다. 공고 전체를 미러링할 때는 이 호출을 쓰세요.
GET/jobs/{job_handle}/pipeline-stagesjobs:read
이 공고의 파이프라인 단계를 순서대로, 현재 각 단계에 있는 지원서 수와 함께 보여 줍니다. 지원서의 stage_id를 해석하려면 이 정보가 필요합니다. 공고마다 한 번 읽어 저장해 두고, 단계 핸들은 저장한 값에서 찾으세요.
{
"object": "list",
"data": [
{
"object": "pipeline_stage",
"id": "stg_9Kd2mXq4Rp8v",
"job_id": "job_7Kd2mXq4Rp8v",
"name": "Applied",
"position": 1,
"type": "applied",
"is_terminal": false,
"application_count": 70
},
{
"object": "pipeline_stage",
"id": "stg_3Rp8vKd2mXq4",
"job_id": "job_7Kd2mXq4Rp8v",
"name": "Screen",
"position": 2,
"type": "screen",
"is_terminal": false,
"application_count": 41
},
{
"object": "pipeline_stage",
"id": "stg_6Tz1wNc8Lm3q",
"job_id": "job_7Kd2mXq4Rp8v",
"name": "Hired",
"position": 5,
"type": "hired",
"is_terminal": true,
"application_count": 2
}
],
"has_more": false,
"next_cursor": null
}단계는 워크스페이스가 아니라 공고에 속합니다. 두 공고에 모두 “Screen”이라는 단계가 있어도 핸들은 서로 다르므로, 사본은 이름이 아니라 핸들을 키로 저장하세요. is_terminal은 Hired, Rejected처럼 지원서가 마지막으로 도착하는 단계를 표시합니다.
GET/jobs/{job_handle}/application-formjobs:read
지원자가 작성한 양식의 필드입니다. 이 정보가 있어야 양식 답변을 해석할 수 있습니다. 답변은 필드 id를 키로 하고, 그 필드의 라벨, 유형, 선택지는 여기에 있습니다.
{
"object": "application_form",
"job_id": "job_7Kd2mXq4Rp8v",
"is_template": false,
"fields": [
{
"id": "name",
"label": "Name",
"type": "text",
"required": true,
"section": null,
"help_text": null,
"options": [],
"max_length": null
},
{
"id": "why_this_role",
"label": "Why are you interested in this role?",
"type": "textarea",
"required": true,
"section": null,
"help_text": "A few sentences is plenty.",
"options": [],
"max_length": 2000
},
{
"id": "shift_preference",
"label": "Which shifts can you work?",
"type": "multiselect",
"required": false,
"section": null,
"help_text": null,
"options": ["Day", "Evening", "Night"],
"max_length": null
}
]
}양식에서 삭제된 필드는 빠지므로, 오늘 읽는 양식은 지금 상태 그대로의 양식입니다. 그래서 이전 지원서에는 더 이상 목록에 없는 필드의 답변이 있을 수 있습니다. 나중에 다시 찾지 말고, 그때 본 라벨을 저장해 두세요.
GET/jobs/{job_handle}/prescreen-questionsjobs:screening:read
전화 사전 스크리닝에서 묻는 질문을 두 그룹으로 보여 줍니다. common에는 모든 공고에서 묻는 기본 질문이, adaptive에는 이 공고만을 위한 질문이 최대 5개 들어 있습니다. source는 각 질문의 출처입니다. template, ai(Tahoe가 공고 설명을 바탕으로 작성), recruiter 중 하나입니다.
{
"object": "prescreen_questions",
"job_id": "job_7Kd2mXq4Rp8v",
"common": [
{
"id": "f19c2a7e-0d4b-4e7a-9c51-3b8f6a2d1e90",
"position": 2,
"prompt": "Are you currently employed?",
"response_key": "currently_employed",
"answer_type": "boolean",
"choices": null,
"required": true,
"source": "template"
}
],
"adaptive": [
{
"id": "8b2a4c6d-1e3f-4a5b-8c7d-9e0f1a2b3c4d",
"position": 1,
"prompt": "How many people have you scheduled on a single shift?",
"response_key": "largest_shift_scheduled",
"answer_type": "number",
"choices": null,
"required": false,
"source": "ai"
}
]
}자체 시스템에서 공고 보내기
POST/jobsjobs:write
자체 시스템의 채용 공고 하나를 만들거나 수정하고, 워크스페이스의 Tahoe 채용 공고 게시판에 게시할 수 있습니다. 이 요청을 이 문서에서는 푸시라고 부릅니다. 별도의 게시 호출은 없습니다. 공고를 "publish": true와 함께 다시 보내면 됩니다. 공고를 삭제하지 않으며, 이 엔드포인트로 보낸 공고만 찾기 때문에 Tahoe에서 만든 공고는 수정하지 않습니다. Tahoe에서 팀이 쓴 공고를 게시, 마감, 재개하려면 라이프사이클 호출을 쓰세요.
쓰기는 API 키로만 할 수 있습니다. Sign in with Tahoe 토큰은 읽을 수 있지만, 연결된 앱의 쓰기가 켜져 있지 않으면 여기서 403 write_requires_api_key를 받습니다. 교차 워크스페이스 키는 다른 호출과 마찬가지로 ?workspace_id=로 워크스페이스를 지정합니다. POST /jobs는 expensive 속도 제한 등급(분당 60건)에 속하며, 선택 사항인 Idempotency-Key 헤더를 받습니다. 헤더 없이 보내면 동작은 예전과 같습니다.
푸시가 공고를 찾는 방법
푸시는 워크스페이스, source.system, source.external_id 세 가지로 공고를 찾습니다. 시스템 이름도 비교 대상이므로, 한 워크스페이스의 두 시스템이 같은 external_id를 써도 됩니다. 같은 source.system과 source.external_id 조합으로 처음 푸시하면 공고가 만들어지고 201을 반환합니다. 이후 같은 조합으로 푸시하면 그 공고가 수정되고 200을 반환합니다. 같은 본문을 두 번 보내도 공고는 그대로이므로 안심하고 재시도할 수 있습니다. 이 엔드포인트로 만든 공고만 찾기 때문에, 고객이 Tahoe에서 만든 공고는 푸시로 절대 바뀌지 않습니다.
요청 본문
본문은 조회한 공고와 같은 필드 이름과 중첩 구조를 쓰지만, 아래 필드만 받습니다. 그 밖의 필드는 id, slug, links 같은 읽기 전용 필드를 포함해 모두 400 invalid_request입니다.
| 필드 | 타입 | 설명 |
|---|---|---|
source.system | string, 필수 | 직접 정하는 시스템 이름입니다. 영문 소문자, 숫자, 밑줄로 된 2~40자입니다. tahoe로 시작하는 이름과 Tahoe가 직접 쓰는 몇몇 이름은 예약되어 있습니다. |
source.external_id | string, 필수 | 자체 시스템의 공고 ID입니다. 1~200자. |
source.canonical_url | string | 자체 사이트의 공고 주소입니다. https://로 시작해야 합니다. |
source.company_name | string | 채용 기업 이름입니다. 최대 200자. |
source.company_logo_url | string | https://로 시작해야 합니다. |
title | string, 필수 | 최대 300자. |
department, team | string | 각각 최대 200자. |
employment_type | string | full_time, part_time, contract, intern, temp 중 하나입니다. |
location_type | string | remote, hybrid, onsite 중 하나입니다. |
locations | string[] | 최대 20개. |
experience_level | string | intern, junior, mid, senior, lead, principal 중 하나입니다. |
years_min, years_max | number | 0~60. |
content.summary | string | 최대 2,000자. |
content.description_md | string | Markdown, 최대 30,000자. |
content.responsibilities, content.requirements, content.nice_to_have | string[] | 각각 최대 50개 항목. |
content.skills_required, content.skills_preferred | string[] | 각각 최대 100개 항목. |
content.benefits | string[] | 최대 50개 항목. |
compensation.salary_min, compensation.salary_max | integer | 통화의 기본 단위로, 0~100,000,000. 최댓값은 최솟값 이상이어야 합니다. |
compensation.currency | string | 영문 세 글자입니다. 기본값 USD. |
compensation.interval | string | hourly, monthly, annual 중 하나입니다. 기본값 annual. |
compensation.equity | string | 자유 텍스트, 최대 200자. |
compensation.commission | boolean | 기본값 false. |
compensation.unit | string | 선택 사항입니다. 보낸다면 major여야 합니다. |
accept_applications | boolean | 기본값 true. |
publish | boolean | 기본값 false. 아래를 참고하세요. |
status | string | closed만 받습니다. 아래를 참고하세요. |
curl -X POST https://tahoe.workonward.com/api/partner/v1/jobs \
-H "Authorization: Bearer $TAHOE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": {
"system": "acme_ats",
"external_id": "REQ-2041",
"canonical_url": "https://example.com/careers/REQ-2041",
"company_name": "Kestrel Labs",
"company_logo_url": "https://files.example.com/kestrel-labs/logo.png"
},
"title": "Field Coordinator",
"department": "Operations",
"employment_type": "full_time",
"location_type": "onsite",
"locations": ["Columbus, Ohio"],
"experience_level": "mid",
"years_min": 2,
"years_max": 5,
"content": {
"summary": "Coordinate field crews and equipment across three sites in central Ohio.",
"description_md": "## About the role\n\nYou will plan daily crew schedules ...",
"responsibilities": ["Build and publish weekly crew schedules", "Track equipment across sites"],
"requirements": ["2+ years coordinating field or site teams", "A valid driver license"],
"nice_to_have": ["Experience with field service scheduling software"],
"skills_required": ["Scheduling", "Microsoft Excel"],
"skills_preferred": ["Inventory tracking"],
"benefits": ["Health, dental and vision cover", "Company vehicle"]
},
"compensation": {
"salary_min": 52000,
"salary_max": 64000,
"currency": "USD",
"interval": "annual"
},
"publish": true
}'{
"object": "job",
"id": "job_5Hw9rTc2Vn6m",
"workspace_id": "wsp_4Kd8sPm2Qx7L",
"slug": "field-coordinator-columbus-3f9a2c41",
"status": "published",
"title": "Field Coordinator",
"department": "Operations",
"team": null,
"employment_type": "full_time",
"location_type": "onsite",
"locations": ["Columbus, Ohio"],
"experience_level": "mid",
"years_min": 2,
"years_max": 5,
"compensation": {
"salary_min": 52000,
"salary_max": 64000,
"currency": "USD",
"interval": "annual",
"unit": "major",
"equity": null,
"commission": false
},
"accept_applications": true,
"external_apply_url": null,
"source": {
"system": "acme_ats",
"external_id": "REQ-2041",
"company_name": "Kestrel Labs",
"company_logo_url": "https://files.example.com/kestrel-labs/logo.png",
"canonical_url": "https://example.com/careers/REQ-2041"
},
"published_at": "2026-10-07T14:03:55.218Z",
"close_at": null,
"created_at": "2026-10-07T14:03:54.901Z",
"updated_at": "2026-10-07T14:03:55.218Z",
"application_count": 0,
"links": {
"self": "/api/partner/v1/jobs/job_5Hw9rTc2Vn6m",
"applications": "/api/partner/v1/jobs/job_5Hw9rTc2Vn6m/applications",
"application_form": "/api/partner/v1/jobs/job_5Hw9rTc2Vn6m/application-form",
"prescreen_questions": "/api/partner/v1/jobs/job_5Hw9rTc2Vn6m/prescreen-questions",
"pipeline_stages": "/api/partner/v1/jobs/job_5Hw9rTc2Vn6m/pipeline-stages",
"sections": "/api/partner/v1/jobs/job_5Hw9rTc2Vn6m/sections"
},
"content": {
"summary": "Coordinate field crews and equipment across three sites in central Ohio.",
"description_md": "## About the role\n\nYou will plan daily crew schedules ...",
"responsibilities": ["Build and publish weekly crew schedules", "Track equipment across sites"],
"requirements": ["2+ years coordinating field or site teams", "A valid driver license"],
"nice_to_have": ["Experience with field service scheduling software"],
"skills_required": ["Scheduling", "Microsoft Excel"],
"skills_preferred": ["Inventory tracking"],
"benefits": ["Health, dental and vision cover", "Company vehicle"]
},
"restricted": ["voice_screening.dial_in_code"],
"restricted_reason": {
"voice_screening.dial_in_code": "scope_required:jobs:screening:read"
}
}응답은 GET /jobs/{job_handle}이 반환하는 것과 똑같은 전체 공고입니다. 나중에 이 공고의 지원서를 읽으려면 id를 저장하세요. 모든 조회 응답에 들어 있는 source.external_id로 다시 찾을 수도 있습니다.
게시와 마감
"publish": true는 초안, 예약, 마감 상태의 공고를 게시합니다. 이미 게시 중인 공고라면 수정 사항만 반영합니다."publish": false를 보내거나 값을 생략해도 게시 중인 공고는 내려가지 않습니다. 이렇게 보낸 새 공고는 Tahoe에 초안으로 남고 채용 공고 게시판에 나타나지 않습니다."status": "closed"는 게시 중이거나 예약된 공고를 마감합니다. 자체 사이트에서 내린 포지션에 쓰세요. 공고의 나머지 내용과 함께 보내야 합니다. 공고는 지원서와 함께 Tahoe에 남고, 공개 페이지에는 포지션이 마감되었다고 표시됩니다.POST /jobs는 게시를 취소하지 않습니다. 공고를 초안으로 되돌리려면 POST /jobs/{job_handle}/unpublish를 쓰세요. API로는 공고를 삭제할 수 없습니다.
푸시로 공고가 게시, 재개, 마감되면 변경 피드에 job.published, job.reopened, job.closed 이벤트가 나타납니다.
설정할 수 없는 항목
- 전화 사전 스크리닝. 푸시한 공고에서는 항상 꺼져 있어, 후보자가 예상하지 못한 전화 스크리닝을 요청받는 일이 없습니다. 리크루터가 Tahoe에서 켤 수 있습니다.
- 슬러그, 6자리 Job ID, 공개 페이지의 메타데이터. Tahoe가 만듭니다.
- 읽기 전용 필드.
id,workspace_id,application_count,external_apply_url,links, 각종 타임스탬프가 해당합니다. 조회한 공고로 본문을 만든다면 이 필드를 빼세요. - closed 외의 상태.
리크루터가 푸시한 공고를 수정하면
누군가 Tahoe에서 푸시한 공고를 수정하면, 수정한 필드는 그 내용이 유지됩니다. 이후의 푸시는 손대지 않은 나머지 필드를 계속 업데이트하므로, Tahoe에서 오타 하나를 고쳤다고 공고 전체가 멈추지는 않습니다.
POST /jobs 오류
| 상태 | 코드 | 조치 |
|---|---|---|
| 400 | invalid_request | 본문 검증에 실패했습니다. 알 수 없는 필드, 빠진 title, https가 아닌 URL, 최솟값보다 작은 최대 급여 등이 원인입니다. errors 목록에 문제가 된 필드가 하나씩 표시됩니다. 본문을 고쳐 다시 보내세요. |
| 400 | unsupported_source_system | source.system이 영문 소문자, 숫자, 밑줄로 된 2~40자 형식이 아니거나, Tahoe가 예약해 둔 이름입니다. 다른 이름을 고르세요. |
| 403 | insufficient_scope | 키에 jobs:write가 없습니다. 이 스코프가 있는 새 키를 만드세요. |
| 403 | write_requires_api_key | Sign in with Tahoe 토큰을 썼거나, Tahoe 사용자와 연결할 수 없는 키를 썼습니다. API 키를 쓰세요. |
| 409 | job_modified_concurrently | 푸시를 저장하는 동안 공고가 바뀌었습니다. 보통 리크루터가 같은 순간에 수정한 경우입니다. 공고를 다시 가져온 뒤 다시 보내세요. 푸시는 반복해도 안전합니다. |
| 409 | invalid_job_transition | 요청한 게시나 마감이 공고의 현재 상태에서는 허용되지 않습니다. 그대로 다시 보내지 말고, 공고를 읽어 상태부터 확인하세요. |
| 429 | rate_limit_exceeded | 1분 안에 expensive 등급 요청이 60건을 넘었습니다. Retry-After에 표시된 시간만큼 기다리세요. |
모든 오류는 같은 형태입니다. 전체 목록은 오류에서 확인하세요.
게시, 게시 취소, 마감, 재개
호출 네 가지가 대시보드의 버튼과 같은 방식으로 공고의 상태를 바꿉니다. 리크루터가 Tahoe에서 쓴 공고를 포함해 워크스페이스의 모든 공고에 쓸 수 있습니다. 그래서 별도의 스코프가 있습니다. POST /jobs는 내 시스템이 만든 공고만 건드리므로, 공고를 푸시할 수 있는 키가 채용 게시판 전체를 마감할 수 있어서는 안 되기 때문입니다. jobs:write만 가진 키는 required_scope: "jobs:manage"와 함께 403 insufficient_scope를 받습니다.
| 호출 | 가능한 현재 상태 | 결과 | 이벤트 |
|---|---|---|---|
publish | draft, scheduled, closed | published | job.published |
unpublish | published, scheduled | draft | job.unpublished |
close | published, scheduled | closed | job.closed |
reopen | closed | published | job.reopened |
이 밖의 조합은 409 invalid_job_transition을 반환합니다. 예를 들면 이미 게시된 공고를 게시하거나 초안을 마감하는 경우입니다. 메시지에는 상태 이름만 있고 공고 내용은 들어 있지 않습니다.
POST/jobs/{job_handle}/publishjobs:manage
POST/jobs/{job_handle}/unpublishjobs:manage
POST/jobs/{job_handle}/closejobs:manage
POST/jobs/{job_handle}/reopenjobs:manage
네 호출 모두 같은 선택 본문을 받고, 200과 함께 GET /jobs/{job_handle}와 같은 형태의 공고를 반환합니다. application_count가 들어 있으며, voice_screening.dial_in_code는 키에 jobs:screening:read도 있을 때만 들어 있습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
expected_updated_at | 타임스탬프 | 선택. 이 공고에서 마지막으로 읽은 updated_at이며 밀리초 단위입니다. 그 사이 공고가 바뀌었다면 호출은 409 stale_resource를 반환하고 아무것도 바꾸지 않습니다. 생략하면 현재 상태를 기준으로 처리합니다. 다른 필드는 400 invalid_request입니다. |
curl -X POST https://tahoe.workonward.com/api/partner/v1/jobs/job_7Kd2mXq4Rp8v/publish \
-H "Authorization: Bearer $TAHOE_API_KEY" \
-H "Idempotency-Key: publish-7Kd2-0001" \
-H "Content-Type: application/json" \
-d '{ "expected_updated_at": "2026-10-08T12:00:00.123Z" }'{
"object": "job",
"id": "job_7Kd2mXq4Rp8v",
"workspace_id": "wsp_4Kd8sPm2Qx7L",
"slug": "data-engineer-remote-ab12cd",
"status": "published",
"title": "Data Engineer",
"published_at": "2026-10-08T12:00:00.123Z",
"updated_at": "2026-10-08T12:00:00.456Z",
"application_count": 0,
"content": { "summary": "...", "description_md": "..." },
"links": { "self": "/api/partner/v1/jobs/job_7Kd2mXq4Rp8v" }
}curl -X POST https://tahoe.workonward.com/api/partner/v1/jobs/job_7Kd2mXq4Rp8v/close \
-H "Authorization: Bearer $TAHOE_API_KEY" \
-H "Idempotency-Key: close-7Kd2-0001"호출마다 하는 일
- 게시는 대시보드의 게시와 같은 일을 합니다. 공개 슬러그를 한 번 만들고, 게시를 취소했다 다시 게시해도 바뀌지 않습니다. 매칭을 위해 공고 설명을 임베딩하고, 공통 전화 스크리닝 질문을 추가하고, 검색용 메타데이터가 없으면 생성하며, 변경이 몇 초 안에 보이도록 공개 채용 게시판의 캐시를 비웁니다. 임베딩, 질문, 메타데이터는 최선을 다하는 작업입니다. 하나가 실패해도 공고는 게시됩니다. 리크루터가 그 공고의 질문을 이미 검토한 경우가 아니면 적응형 사전 스크리닝 질문도 생성되며, 이때 대시보드와 마찬가지로 워크스페이스의 AI 할당량을 씁니다.
- 게시 취소는 공고를
draft로 되돌리므로 공개 페이지가 열리지 않습니다. 지원서는 모두 유지됩니다. - 마감은 공개 페이지를 그대로 두고 포지션이 마감되었다고 표시합니다. 지원서는 모두 유지되며, 지원자를 탈락시키거나 메시지를 보내지 않습니다.
- 재개는 마감된 공고를 다시 게시합니다. 마감된 공고는 이미 공개된 적이 있으므로 완성도를 검사하지 않습니다.
- 네 호출 모두 후보자나 지원자에게 이메일, 문자 메시지, 알림을 보내지 않습니다.
- 호출마다 대시보드 버튼이 남기는 것과 같은 활동 항목(
job_published,job_unpublished,job_closed,job_reopened)이 키를 만든 사람의 이름으로 남습니다. 누가 눌렀든 활동 피드는 똑같이 보입니다. publish는 설명을 임베딩하므로expensive속도 제한 등급에 속합니다. 나머지 셋은 일반 등급을 씁니다.
라이프사이클 호출의 오류
| 상태 | 코드 | 조치 |
|---|---|---|
| 400 | invalid_request | 알 수 없는 본문 필드이거나, expected_updated_at이 타임스탬프가 아닙니다. |
| 400 | invalid_idempotency_key | Idempotency-Key 헤더가 허용된 문자 8~255자가 아닙니다. |
| 403 | insufficient_scope | 키에 jobs:manage가 없습니다. |
| 403 | write_requires_api_key | Sign in with Tahoe 토큰이거나, Tahoe 사용자와 연결되지 않은 자격 증명입니다. |
| 404 | not_found | 이 워크스페이스에 그런 공고가 없습니다. 다른 워크스페이스의 공고와 삭제된 공고도 같은 응답을 받습니다. |
| 409 | invalid_job_transition | 공고의 현재 상태에서는 이 호출을 할 수 없습니다. 공고를 먼저 읽으세요. |
| 409 | stale_resource | expected_updated_at이 더 이상 맞지 않습니다. 공고를 다시 읽고 다시 시도하세요. |
| 409 | job_modified_concurrently | 확인과 쓰기 사이에 공고가 바뀌었습니다. 다시 읽고 다시 시도하세요. |
| 409 | idempotency_key_reused | 같은 키를 다른 요청에 썼습니다. |
| 409 | idempotency_in_flight | 같은 키의 첫 요청이 아직 실행 중입니다. 잠시 후 다시 시도하세요. |
| 422 | job_not_publishable | publish에서만 발생합니다. 공개 페이지에 필요한 내용이 공고에 없습니다. reasons에 빠진 항목이 나옵니다. |
| 429 | rate_limit_exceeded | 한도를 넘었습니다. Retry-After만큼 기다리세요. |
{
"detail": {
"code": "job_not_publishable",
"type": "invalid_request",
"message": "The job is missing: description",
"reasons": ["description"]
}
}reasons에는 제목이 비어 있으면 title이, description_md와 summary 모두에 텍스트가 없으면 description이 들어 있습니다. 공개 페이지가 최소한 있어야 하는 내용이며, 서버 측 검사가 없는 제품의 게시 버튼 규칙을 옮겨 온 것이 아닙니다.
호출마다 이벤트가 하나씩 발생합니다. job.published, job.unpublished, job.closed, job.reopened 중 하나입니다. 식별자와 status, previous_status만 들어 있고 origin이 함께 옵니다. 거부된 호출은 이벤트를 만들지 않습니다. Idempotency-Key를 보내면 같은 요청을 반복할 때 첫 응답이 Idempotent-Replayed: true와 함께 돌아오고, 상태 전환도 이벤트도 다시 일어나지 않습니다. 키가 없으면 반복 호출이 상태 규칙에 걸립니다. 게시를 두 번 하면 두 번째는 409 invalid_job_transition입니다.
관련 이벤트
변경 피드에는 공고 이벤트 유형이 일곱 가지 있습니다. job.published, job.updated, job.closed, job.reopened, job.unpublished, job.deleted, job.sections_updated입니다. 일곱 가지 모두 jobs:read만 있으면 됩니다. 타이머를 걸어 모든 공고를 다시 읽지 말고, 이 이벤트를 updated_after와 함께 쓰세요. 내가 쓴 변경이 만든 이벤트에는 origin이 있으므로 알아보고 건너뛸 수 있습니다.
채용 공고 게시판 전체를 단계별로 미러링하는 방법은 채용 게시판 미러링을 참고하세요.