본문으로 건너뛰기

채용 공고

채용 공고와 섹션, 단계, 양식을 읽고, 내 시스템의 공고를 보내고, 공고를 게시, 마감, 재개하세요.

채용 공고를 읽는 엔드포인트가 일곱 개, 바꾸는 엔드포인트가 다섯 개 있습니다. 목록은 일부러 가볍게, 단건 조회는 전체 내용을 담도록 만들었습니다. 채용 공고 게시판은 목록으로 미러링하고, 실제로 보여 줄 공고만 전체 내용을 가져오세요.

POST /jobs는 ATS나 채용 사이트처럼 자체 시스템에서 온 공고를 만들거나 수정합니다. jobs:write 스코프가 필요합니다. 라이프사이클 호출 네 가지는 팀이 Tahoe에서 쓴 공고를 포함해 워크스페이스의 모든 공고를 게시, 게시 취소, 마감, 재개합니다. 이쪽은 jobs:manage가 필요합니다.

GET/jobsjobs:read

워크스페이스의 채용 공고를 최근 수정된 순서로 보여 줍니다. 기본적으로 published와 closed 공고만 반환하므로, 이 목록을 미러링하는 게시판에 고객이 작성 중인 초안이 실수로 노출되는 일이 없습니다.

파라미터타입설명
statusstring쉼표로 구분합니다. 기본값은 published,closed입니다. 다른 상태를 받으려면 include_unpublished=true가 필요합니다.
include_unpublishedboolean초안 등 공개되지 않은 상태도 받겠다고 선택합니다. 이 값 없이 그런 상태를 요청하면 400 unpublished_requires_opt_in입니다.
departmentstring정확히 일치하는 값만 찾습니다.
location_typestringremote, hybrid, onsite 중 하나입니다.
employment_typestringfull_time, part_time, contract, intern, temp 중 하나입니다.
qstring공고 전체를 대상으로 하는 텍스트 검색입니다.
updated_aftertimestamp이 시점 이후 변경된 공고만 반환합니다. 전체를 다시 넘겨 보는 대신 증분 동기화에 쓰세요.
published_aftertimestamp이 시점 이후 게시된 공고만 반환합니다.
limitinteger기본값 25, 최대 100.
cursorstring이전 페이지에서 받은 값입니다. 같은 필터를 함께 보내세요.
요청
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.systemstring, 필수직접 정하는 시스템 이름입니다. 영문 소문자, 숫자, 밑줄로 된 2~40자입니다. tahoe로 시작하는 이름과 Tahoe가 직접 쓰는 몇몇 이름은 예약되어 있습니다.
source.external_idstring, 필수자체 시스템의 공고 ID입니다. 1~200자.
source.canonical_urlstring자체 사이트의 공고 주소입니다. https://로 시작해야 합니다.
source.company_namestring채용 기업 이름입니다. 최대 200자.
source.company_logo_urlstringhttps://로 시작해야 합니다.
titlestring, 필수최대 300자.
department, teamstring각각 최대 200자.
employment_typestringfull_time, part_time, contract, intern, temp 중 하나입니다.
location_typestringremote, hybrid, onsite 중 하나입니다.
locationsstring[]최대 20개.
experience_levelstringintern, junior, mid, senior, lead, principal 중 하나입니다.
years_min, years_maxnumber0~60.
content.summarystring최대 2,000자.
content.description_mdstringMarkdown, 최대 30,000자.
content.responsibilities, content.requirements, content.nice_to_havestring[]각각 최대 50개 항목.
content.skills_required, content.skills_preferredstring[]각각 최대 100개 항목.
content.benefitsstring[]최대 50개 항목.
compensation.salary_min, compensation.salary_maxinteger통화의 기본 단위로, 0~100,000,000. 최댓값은 최솟값 이상이어야 합니다.
compensation.currencystring영문 세 글자입니다. 기본값 USD.
compensation.intervalstringhourly, monthly, annual 중 하나입니다. 기본값 annual.
compensation.equitystring자유 텍스트, 최대 200자.
compensation.commissionboolean기본값 false.
compensation.unitstring선택 사항입니다. 보낸다면 major여야 합니다.
accept_applicationsboolean기본값 true.
publishboolean기본값 false. 아래를 참고하세요.
statusstringclosed만 받습니다. 아래를 참고하세요.
요청
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
  }'
응답: 201 Created
{
  "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 오류

상태코드조치
400invalid_request본문 검증에 실패했습니다. 알 수 없는 필드, 빠진 title, https가 아닌 URL, 최솟값보다 작은 최대 급여 등이 원인입니다. errors 목록에 문제가 된 필드가 하나씩 표시됩니다. 본문을 고쳐 다시 보내세요.
400unsupported_source_systemsource.system이 영문 소문자, 숫자, 밑줄로 된 2~40자 형식이 아니거나, Tahoe가 예약해 둔 이름입니다. 다른 이름을 고르세요.
403insufficient_scope키에 jobs:write가 없습니다. 이 스코프가 있는 새 키를 만드세요.
403write_requires_api_keySign in with Tahoe 토큰을 썼거나, Tahoe 사용자와 연결할 수 없는 키를 썼습니다. API 키를 쓰세요.
409job_modified_concurrently푸시를 저장하는 동안 공고가 바뀌었습니다. 보통 리크루터가 같은 순간에 수정한 경우입니다. 공고를 다시 가져온 뒤 다시 보내세요. 푸시는 반복해도 안전합니다.
409invalid_job_transition요청한 게시나 마감이 공고의 현재 상태에서는 허용되지 않습니다. 그대로 다시 보내지 말고, 공고를 읽어 상태부터 확인하세요.
429rate_limit_exceeded1분 안에 expensive 등급 요청이 60건을 넘었습니다. Retry-After에 표시된 시간만큼 기다리세요.

모든 오류는 같은 형태입니다. 전체 목록은 오류에서 확인하세요.

게시, 게시 취소, 마감, 재개

호출 네 가지가 대시보드의 버튼과 같은 방식으로 공고의 상태를 바꿉니다. 리크루터가 Tahoe에서 쓴 공고를 포함해 워크스페이스의 모든 공고에 쓸 수 있습니다. 그래서 별도의 스코프가 있습니다. POST /jobs는 내 시스템이 만든 공고만 건드리므로, 공고를 푸시할 수 있는 키가 채용 게시판 전체를 마감할 수 있어서는 안 되기 때문입니다. jobs:write만 가진 키는 required_scope: "jobs:manage"와 함께 403 insufficient_scope를 받습니다.

호출가능한 현재 상태결과이벤트
publishdraft, scheduled, closedpublishedjob.published
unpublishpublished, scheduleddraftjob.unpublished
closepublished, scheduledclosedjob.closed
reopenclosedpublishedjob.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" }'
응답: 200 (일부)
{
  "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 속도 제한 등급에 속합니다. 나머지 셋은 일반 등급을 씁니다.

라이프사이클 호출의 오류

상태코드조치
400invalid_request알 수 없는 본문 필드이거나, expected_updated_at이 타임스탬프가 아닙니다.
400invalid_idempotency_keyIdempotency-Key 헤더가 허용된 문자 8~255자가 아닙니다.
403insufficient_scope키에 jobs:manage가 없습니다.
403write_requires_api_keySign in with Tahoe 토큰이거나, Tahoe 사용자와 연결되지 않은 자격 증명입니다.
404not_found이 워크스페이스에 그런 공고가 없습니다. 다른 워크스페이스의 공고와 삭제된 공고도 같은 응답을 받습니다.
409invalid_job_transition공고의 현재 상태에서는 이 호출을 할 수 없습니다. 공고를 먼저 읽으세요.
409stale_resourceexpected_updated_at이 더 이상 맞지 않습니다. 공고를 다시 읽고 다시 시도하세요.
409job_modified_concurrently확인과 쓰기 사이에 공고가 바뀌었습니다. 다시 읽고 다시 시도하세요.
409idempotency_key_reused같은 키를 다른 요청에 썼습니다.
409idempotency_in_flight같은 키의 첫 요청이 아직 실행 중입니다. 잠시 후 다시 시도하세요.
422job_not_publishablepublish에서만 발생합니다. 공개 페이지에 필요한 내용이 공고에 없습니다. reasons에 빠진 항목이 나옵니다.
429rate_limit_exceeded한도를 넘었습니다. Retry-After만큼 기다리세요.
422 Unprocessable
{
  "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이 있으므로 알아보고 건너뛸 수 있습니다.

채용 공고 게시판 전체를 단계별로 미러링하는 방법은 채용 게시판 미러링을 참고하세요.