---
title: "Create job"
method: POST
path: "/v3/jobs"
tags: ["Jobs"]
---

# Create job

`POST /v3/jobs`

## Request

- Base URL: `https://harvest.greenhouse.io`
- URL: `https://harvest.greenhouse.io/v3/jobs`
- Auth: one of:
  - OAuth 2
    - Authorization code: `https://auth.greenhouse.io/authorize`
    - Client credentials
    - Token URL: `https://auth.greenhouse.io/token` scopes: `harvest:jobs:create`
  - HTTP bearer

Create a new job from an existing template. `template_job_id` and `number_of_openings` are required; the template supplies the interview plan, scorecards, hiring team, and approval workflow, since Greenhouse Recruiting remains the source of truth for those configurations. One opening is created for each requested slot, optionally tagged with a partner `opening_ids` entry. Department and offices can be set either by Greenhouse id (`department_id`, `office_ids`) or by partner `external_id` (`external_department_id`, `external_office_ids`) — the id and external-id forms are mutually exclusive. The legacy `confidential` flag cannot be set through this endpoint. Required job custom fields the template leaves blank must be supplied in `custom_fields`, and select values must be active options (`GET /v3/custom_field_options?active=true`); a 422 response lists the offending fields in `errors`.

## Request body

- object
  - `template_job_id` integer, required: Id of an existing job (typically a template) used as the source for this job's interview plan, scorecards, hiring team, and approval workflow. Required — Greenhouse Recruiting remains the source of truth for these configurations, so new jobs always inherit them from a template.
  - `number_of_openings` integer, required: Number of openings to create on the new job. Each opening can be filled by a single hire. The total open openings across the job cannot exceed Greenhouse's internal limit of 200; requests that would exceed it are rejected.
  - `job_post_name` string: External-facing title for the job's initial internal job post (the listing shown on the company's careers site). Defaults to `job_name` when omitted.
  - `notes` string: Internal HTML notes about the job, surfaced to the hiring team in the Greenhouse UI.
  - `job_name` string: Internal job title shown to the hiring team (e.g. `Senior Backend Engineer`). When omitted, the template job's name is used.
  - `department_id` integer, nullable: Id of the department to assign the job to. Mutually exclusive with `external_department_id`.
  - `external_department_id` string, nullable: Partner-supplied `external_id` of the department to assign the job to. Resolved to a Greenhouse `department_id` in the caller's organization; unknown values are rejected. Mutually exclusive with `department_id`.
  - `office_ids` integer[], nullable: Ids of the offices to assign the job to. A job can span multiple offices. Mutually exclusive with `external_office_ids`.
  - `external_office_ids` string[], nullable: Partner-supplied `external_id` values of the offices to assign the job to. Each entry is resolved to a Greenhouse `office_id` in the caller's organization; unknown values are rejected. Mutually exclusive with `office_ids`.
  - `requisition_id` string, nullable: Partner-supplied external identifier for the requisition (e.g. an HRIS or ATS code). Free-form string and not required to be unique across the organization.
  - `opening_ids` string[], nullable: Optional partner-supplied identifiers for the openings being created — one entry per new opening, paired positionally with `number_of_openings`. Each value is stored on the resulting opening as its `opening_id` (a free-form string, not the Greenhouse opening id) and may be `null`. Greenhouse enforces an internal limit of 200 open openings per job; requests that would exceed it fail.
  - `custom_fields` union[]: Values to set on this job's job custom fields. Each entry identifies the field by `custom_field_id` or `name_key` and supplies a `value` whose shape matches the field's `value_type` (see `GET /v3/custom_fields?field_type=job`). Every field marked `required` that the template job leaves blank must be supplied here. Single-select and multi-select values must name an active option: read them from `GET /v3/custom_field_options?active=true`, because archived options are listed unless `active=true` is passed and are rejected on write.
    - union
      - object
        - `name_key` string, required: Required if custom_field_id is not provided. Exactly one of name_key or custom_field_id must be specified.
        - `custom_field_id` integer: Required if name_key is not provided. Exactly one of name_key or custom_field_id must be specified.
        - `value` union
          - string
          - string
          - string
          - string
          - string
          - boolean
          - number: User ID
          - union
            - number
            - string
          - union
            - string[]
            - number[]
          - number
          - object
            - `amount` number, required
            - `currency_code` string, required
          - object
            - `min_amount` number, required
            - `max_amount` number, required
            - `currency_code` string, required
          - object
            - `min_value` number, required
            - `max_value` number, required
      - object
        - `name_key` string: Required if custom_field_id is not provided. Exactly one of name_key or custom_field_id must be specified.
        - `custom_field_id` integer, required: Required if name_key is not provided. Exactly one of name_key or custom_field_id must be specified.
        - `value` union
          - string
          - string
          - string
          - string
          - string
          - boolean
          - number: User ID
          - union
            - number
            - string
          - union
            - string[]
            - number[]
          - number
          - object
            - `amount` number, required
            - `currency_code` string, required
          - object
            - `min_amount` number, required
            - `max_amount` number, required
            - `currency_code` string, required
          - object
            - `min_value` number, required
            - `max_value` number, required
  - `ignore_custom_field_requirements` boolean: If `true`, skips validation that required job custom fields have a value. Useful for integrations that only intend to sync a subset of fields and rely on the template job or a later request to supply the rest. Restricted to integration service-account credentials; other callers are rejected with a 422.
  - `ignore_approver_permission_check` boolean: If `true`, skips the check that a user set as a job or offer approver via `custom_fields` has permission to view/edit this job. Lets an integration assign an approver without first broadening that user's Greenhouse permissions. Restricted to integration service-account credentials; other callers are rejected with a 422.

## Response `201`

Successful

- object
  - `id` integer
  - `created_at` string, date-time
  - `updated_at` string, date-time
  - `name` string: Internal job title shown to the hiring team in Greenhouse (e.g. `Senior Backend Engineer`). Distinct from the external-facing title on each `job_post`.
  - `requisition_id` string, nullable: Partner-supplied external identifier for the requisition (e.g. an HRIS or ATS code). Free-form string, not unique across the organization, and `null` when no external id has been set.
  - `notes` string, nullable: Internal HTML notes about the job, surfaced to the hiring team in the Greenhouse UI. Not exposed on public job posts.
  - `confidential` boolean: If `true`, the job is restricted to users explicitly granted access on the Hiring Team. The legacy Confidential Jobs feature has been sunset — this flag cannot be set on new jobs and is preserved for jobs that already had it enabled.
  - `status` 'open' | 'draft' | 'closed': Lifecycle status of the job. `draft` while it is being scaffolded, `open` once it has at least one open opening, and `closed` after every opening is closed. A job moves to `closed` automatically when its last open opening is closed via `PATCH /v3/openings/{id}`.
  - `opened_at` string, date-time, nullable: Timestamp the job first transitioned to `open`, in ISO 8601. `null` while the job is still in `draft`.
  - `closed_at` string, date-time, nullable: Timestamp the job most recently transitioned to `closed`, in ISO 8601. `null` for jobs that are still `open` or `draft`.
  - `is_template` boolean, nullable: If `true`, this job is a template used as the source for new jobs rather than a real requisition. Templates do not accept applications; reference them via `template_job_id` on `POST /v3/jobs`.
  - `copied_from_id` integer, nullable: Id of the job (typically a template) this job was copied from on creation. `null` when the job was not created from another job.
  - `department_id` integer, nullable: Id of the department this job is assigned to. `null` when no department is set.
  - `external_department_id` string, nullable: `external_id` of the department this job is assigned to, for partners that reconcile jobs against an HRIS by external id. Mirrors the `external_department_id` accepted on create and update. `null` when no department is set or the department has no external id.
  - `office_ids` integer[], nullable: Ids of the offices this job is assigned to. A job can span multiple offices; empty array or `null` when no offices are set.
  - `external_office_ids` string[]: `external_id`s of the offices this job is assigned to, for partners that reconcile jobs against an HRIS by external id. Mirrors the `external_office_ids` accepted on create and update. Offices with no external id set are omitted, so this can be shorter than `office_ids`.
  - `custom_fields` object, nullable: Job custom field values keyed by each field's `name_key`. Only includes fields that apply to this job's offices and departments, by the same rules the Job Info page uses. A field restricted to offices or departments (see `GET /v3/custom_field_offices` and `GET /v3/custom_field_departments`) is left out unless the job is in one of them or in a child of one. A field restricted on both needs a match on each. A job with no offices or departments includes every field. `PATCH /v3/jobs/{id}` returns a 422 for a non-null value on a field left out here. A field that applies but has no value is returned with `value: null`.

## Other responses

- `401`: Unauthorized
- `403`: Forbidden
- `422`: Unprocessable Content

---

[API](https://skmtc.dev/greenhouse/apis/harvest-api.md) - [All operations](https://skmtc.dev/greenhouse/apis/harvest-api/llms.txt) - [OpenAPI document](https://skmtc.dev/greenhouse/apis/harvest-api/revisions/822822e3f771?raw)
