---
title: "Bulk create jobs"
method: POST
path: "/v3/jobs/bulk"
tags: ["Jobs"]
---

# Bulk create jobs

`POST /v3/jobs/bulk`

## Request

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

Asynchronously create many jobs in a single call, typically for HRIS sync workflows that open a batch of requisitions at once. Each item follows the same shape as `POST /v3/jobs`, with `template_job_id` and `number_of_openings` required, and is processed independently. A `bulk_action_uuid` is returned immediately. Per-item success or failure is reported through `GET /v3/bulk_requests/{bulk_action_uuid}`.

## Request body

- object
  - `callback_url` string: Optional HTTPS URL Greenhouse will POST to when every row in `data` has finished. Omit to poll `GET /v3/bulk_requests/{bulk_action_uuid}` instead.
  - `data` object[], required: Array of single-resource create payloads to process. Each item must match the schema of the matching non-bulk POST endpoint.
    - `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
              - …
            - union
              - …
            - number
            - object
              - …
            - object
              - …
            - object
              - …
        - 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
              - …
            - union
              - …
            - number
            - object
              - …
            - object
              - …
            - object
              - …
    - `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 `202`

Accepted

- object
  - `bulk_action_uuid` string: UUID assigned to the new bulk request. Use this with `GET /v3/bulk_requests/{bulk_action_uuid}` to monitor progress and retrieve per-row results.
  - `status` string: Initial lifecycle state of the new bulk request, typically `building` immediately after creation. See the bulk request response schema for the full set of values.
  - `status_url` string: Relative path that returns the current status of the bulk request. Append this to your Harvest base URL to fetch the latest state.

## Other responses

- `401`: Unauthorized
- `413`: Payload is larger than 10MB
- `422`: Validation Error
- `429`: Client has more than 5 active jobs

---

[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)
