> ## Documentation Index
> Fetch the complete documentation index at: https://opentrain.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Update Job Draft

> Fill in or correct draft fields in response to validation prompts.

Patches any subset of fields on an unpublished draft and returns refreshed validation. This is the second half of the gap-filling loop: take each missing field's `updateKeys` and `enumValues` from the [create response](/docs/developers/api-reference/job-drafts/create), ask your human the `prompt`, and patch the answers. Repeat until `validation.publishReady` is `true`, then [publish](/docs/developers/api-reference/jobs/publish).

The accepted keys and enum values are machine-discoverable at [`GET /job-drafts/capabilities`](/docs/developers/api-reference/job-drafts/capabilities) (`draft.updateKeys` and `draft.enums`).

**Requirements:** `jobs:write` scope + the `public_api_job_drafting` feature. The job must be an unpublished draft you own — to edit a live job use [`PATCH /jobs/{id}`](/docs/developers/api-reference/jobs/update).

## Request

<ParamField path="jobId" type="string" required>
  The draft job ID from the create response.
</ParamField>

The body is a JSON object with one or more draft fields. Unknown keys are rejected (`400` with zod issue details). Commonly patched keys:

<ParamField body="jobTitle" type="string">
  Job title.
</ParamField>

<ParamField body="jobDescription" type="string">
  Full description text.
</ParamField>

<ParamField body="paymentType" type="string">
  `PAY_PER_LABEL`, `PAY_PER_HOUR`, or `FIXED_PRICE`. Pair with the matching rate field below.
</ParamField>

<ParamField body="pricePerHour" type="number">
  Hourly rate in USD (for `PAY_PER_HOUR`).
</ParamField>

<ParamField body="pricePerLabel" type="number">
  Per-label rate in USD (for `PAY_PER_LABEL`).
</ParamField>

<ParamField body="fixedPrice" type="number">
  Total fixed price in USD (for `FIXED_PRICE`).
</ParamField>

<ParamField body="experienceLevel" type="string">
  `EXPERT`, `ENTRY_LEVEL`, `INTERMEDIATE`, or `ANY_EXPERIENCE_LEVEL`.
</ParamField>

<ParamField body="dataVolumeUnit" type="string">
  `HOURS_OF_RECORDING_AUDIO_VIDEO`, `NUMBER_OF_FILES`, `NUMBER_OF_WORDS`, or `UNKNOWN_NOT_SPECIFIED`.
</ParamField>

<ParamField body="languages" type="string[]">
  Required languages.
</ParamField>

<ParamField body="countries" type="string[]">
  Allowed countries.
</ParamField>

<ParamField body="labelTypes" type="string[]">
  Label/annotation types.
</ParamField>

<ParamField body="headcount" type="number">
  Number of AI trainers to hire.
</ParamField>

<ParamField body="dataVolume" type="number">
  Quantity of data, in `dataVolumeUnit` units.
</ParamField>

The full key list also includes `labelingOverview`, `datasetDescription`, `dataType`, `subjectMatter`, `labelingSoftware`, `aiInterviewRequirements`, `budgetRange`, `workloadDesc`, `timeRequirement`, `projectDuration`, `freelancerType`, `projectScope`, `visibility`, and more — fetch `draft.updateKeys` from [capabilities](/docs/developers/api-reference/job-drafts/capabilities) for the authoritative set. Enum fields must use the exact canonical values above.

## Response

<ResponseField name="ok" type="boolean">
  `true` on success.
</ResponseField>

<ResponseField name="jobId" type="string">
  The draft job ID.
</ResponseField>

<ResponseField name="status" type="string">
  Still `DRAFT`.
</ResponseField>

<ResponseField name="draftUrl" type="string">
  In-app URL of the draft editor.
</ResponseField>

<ResponseField name="validation" type="object">
  Refreshed validation — same shape as the create response: `publishReady`, `issueCount`, `missingFieldCount`, `missingFields[]` (with `prompt`/`type`/`enumValues`/`updateKeys`), `issues[]`.
</ResponseField>

## Errors

| Status | `code`         | Meaning                                                                                                |
| ------ | -------------- | ------------------------------------------------------------------------------------------------------ |
| `400`  | `BAD_REQUEST`  | Empty body, invalid JSON, unknown keys or wrong types (`details` carries zod issues), or no fields set |
| `401`  | `UNAUTHORIZED` | Missing or invalid token                                                                               |
| `403`  | `FORBIDDEN`    | Missing `jobs:write` scope, feature disabled, or the job belongs to another account                    |
| `404`  | `NOT_FOUND`    | No such job                                                                                            |

<RequestExample>
  ```bash curl theme={null}
  curl -sS -X PATCH https://app.opentrain.ai/api/public/v1/job-drafts/<JOB_ID> \
    -H "Authorization: Bearer $OT_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "experienceLevel": "INTERMEDIATE",
      "paymentType": "PAY_PER_LABEL",
      "pricePerLabel": 0.06
    }'
  ```

  ```bash CLI theme={null}
  opentrain jobs draft update --job-id <JOB_ID> \
    --set experienceLevel=INTERMEDIATE \
    --set paymentType=PAY_PER_LABEL \
    --set pricePerLabel=0.06 --json
  ```

  ```json MCP: opentrain_update_job_draft_fields theme={null}
  {
    "jobId": "<JOB_ID>",
    "patch": {
      "experienceLevel": "INTERMEDIATE",
      "paymentType": "PAY_PER_LABEL",
      "pricePerLabel": 0.06
    }
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "ok": true,
    "jobId": "<JOB_ID>",
    "status": "DRAFT",
    "draftUrl": "https://app.opentrain.ai/job-post?jobId=<JOB_ID>",
    "validation": {
      "publishReady": true,
      "issueCount": 0,
      "missingFieldCount": 0,
      "missingFields": [],
      "issues": []
    }
  }
  ```

  ```json 400 (invalid payload) theme={null}
  {
    "error": "Invalid payload",
    "code": "BAD_REQUEST",
    "requestId": "<REQUEST_ID>",
    "details": {
      "formErrors": [],
      "fieldErrors": {
        "experienceLevel": [
          "Invalid enum value. Expected 'EXPERT' | 'ENTRY_LEVEL' | 'INTERMEDIATE' | 'ANY_EXPERIENCE_LEVEL', received 'SENIOR'"
        ]
      }
    }
  }
  ```
</ResponseExample>
