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

# Jobs

> Create and manage job postings and ATS assignments

## Overview

Jobs represent open positions that candidates can be screened for. You can create jobs, attach interviews and screening questionnaires, assign ATS jobs, and manage job status. Job Families (read-only) provide a taxonomy layer that groups related jobs.

**Base URL:** `https://api.prod.qualifi.hr`

**Authentication:** All requests require an `x-api-key` header.

```
x-api-key: YOUR_API_KEY
```

<Callout type="info">
  All responses follow the envelope: `{ "data": { ... }, "meta": { "requestId": "uuid", "timestamp": "ISO-8601" } }`.
</Callout>

## Create Job

Create a new job posting.

**Endpoint:** `POST /qsi/gather/jobs`

<ParamField name="title" type="string" required>
  Job title
</ParamField>

<ParamField name="type" type="string" required>
  Job type. One of `requisition`, `pipeline`, `template`.
</ParamField>

<ParamField name="department" type="string">
  Department the job belongs to
</ParamField>

<ParamField name="location" type="string[]">
  List of location strings for the job
</ParamField>

<ParamField name="workModel" type="string">
  Work model (e.g. `"remote"`, `"hybrid"`, `"onsite"`)
</ParamField>

<ParamField name="description" type="string">
  Full HTML or plain-text description of the job
</ParamField>

<ParamField name="descriptionJson" type="object">
  Structured JSON representation of the job description
</ParamField>

<ParamField name="requirements" type="string">
  Full HTML or plain-text requirements
</ParamField>

<ParamField name="requirementsJson" type="object">
  Structured JSON representation of requirements
</ParamField>

<ParamField name="status" type="string">
  Initial job status. At creation, only `draft`, `pending_approval`, and `active` are accepted (other statuses return `400`, and `approved` returns `403`). Advanced and terminal states are reachable only via update transitions. See the [status limitation callout](#status-limitation) below.
</ParamField>

<ParamField name="tags" type="string[]">
  Arbitrary tags for filtering or labelling
</ParamField>

<ParamField name="applyAction" type="string">
  Apply button behavior. One of `apply_first`, `interview_first`, `chat_to_apply`.
</ParamField>

<ParamField name="interviewId" type="string">
  UUID of an interview to attach to this job
</ParamField>

<ParamField name="meetingId" type="string">
  UUID of a meeting to attach to this job
</ParamField>

<ParamField name="screeningQuestionnaireId" type="string">
  UUID of a screening questionnaire to attach
</ParamField>

<ParamField name="knowledgeBaseIds" type="string[]">
  UUIDs of knowledge base entries to associate
</ParamField>

<ParamField name="jobFamilyIds" type="string[]">
  UUIDs of job families to associate this job with
</ParamField>

<ParamField name="assignedRecruiterId" type="string">
  UUID of the recruiter to assign
</ParamField>

<ParamField name="teamId" type="string">
  UUID of the team that owns this job
</ParamField>

<ParamField name="userId" type="string">
  UUID of the user creating the job (defaults to the API key owner)
</ParamField>

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST https://api.prod.qualifi.hr/qsi/gather/jobs \
      -H "x-api-key: ${API_KEY}" \
      -H "Content-Type: application/json" \
      -d '{
        "title": "Senior Software Engineer",
        "type": "requisition",
        "department": "Engineering",
        "location": ["San Francisco, CA"],
        "workModel": "hybrid"
      }'
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch('https://api.prod.qualifi.hr/qsi/gather/jobs', {
      method: 'POST',
      headers: {
        'x-api-key': apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        title: 'Senior Software Engineer',
        type: 'requisition',
        department: 'Engineering',
        location: ['San Francisco, CA'],
        workModel: 'hybrid'
      })
    });
    const data = await response.json();
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.post(
        'https://api.prod.qualifi.hr/qsi/gather/jobs',
        headers={
            'x-api-key': api_key,
            'Content-Type': 'application/json'
        },
        json={
            'title': 'Senior Software Engineer',
            'type': 'requisition',
            'department': 'Engineering',
            'location': ['San Francisco, CA'],
            'workModel': 'hybrid'
        }
    )
    data = response.json()
    ```
  </Tab>
</Tabs>

<CodeGroup>
  ```json Request theme={null}
  {
    "title": "Senior Software Engineer",
    "type": "requisition",
    "department": "Engineering",
    "location": ["San Francisco, CA"],
    "workModel": "hybrid",
    "status": "draft",
    "tags": ["backend", "python"]
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "job": {
        "id": "job-uuid",
        "title": "Senior Software Engineer",
        "type": "requisition",
        "department": "Engineering",
        "location": ["San Francisco, CA"],
        "workModel": "hybrid",
        "status": "draft",
        "tags": ["backend", "python"],
        "organizationId": "org-uuid",
        "teamId": "team-uuid",
        "createdById": "user-uuid",
        "createdAt": "2024-01-01T00:00:00Z",
        "updatedAt": "2024-01-01T00:00:00Z"
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2024-01-01T00:00:00Z"
    }
  }
  ```
</CodeGroup>

## List Jobs

List jobs with optional filtering and pagination.

**Endpoint:** `GET /qsi/gather/jobs`

<ParamField query="teamId" type="string">
  Filter by team UUID
</ParamField>

<ParamField query="page" type="number">
  Page number (default: 1)
</ParamField>

<ParamField query="pageSize" type="number">
  Items per page (default: 50, max: 100)
</ParamField>

<ParamField query="type" type="string">
  Filter by job type
</ParamField>

<ParamField query="status" type="string">
  Filter by job status
</ParamField>

<ParamField query="department" type="string">
  Filter by department
</ParamField>

<ParamField query="location" type="string">
  Filter by location string
</ParamField>

<ParamField query="search" type="string">
  Full-text search on job title and description
</ParamField>

<Callout type="info">
  **Pagination:** Use `page` and `pageSize` query params. The response includes a `pagination` object with `page`, `pageSize`, `total`, and `totalPages` fields.
</Callout>

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "jobs": [
        {
          "id": "job-uuid",
          "title": "Senior Software Engineer",
          "type": "requisition",
          "department": "Engineering",
          "status": "active",
          "organizationId": "org-uuid",
          "teamId": "team-uuid",
          "createdAt": "2024-01-01T00:00:00Z",
          "updatedAt": "2024-01-01T00:00:00Z"
        }
      ],
      "pagination": {
        "page": 1,
        "pageSize": 50,
        "total": 42,
        "totalPages": 1
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2024-01-01T00:00:00Z"
    }
  }
  ```
</CodeGroup>

## Get Job

Retrieve a single job by ID.

**Endpoint:** `GET /qsi/gather/jobs/{id}`

<ParamField path="id" type="string" required>
  UUID of the job
</ParamField>

<ParamField query="teamId" type="string">
  Team UUID (used for access scoping)
</ParamField>

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "job": {
        "id": "job-uuid",
        "title": "Senior Software Engineer",
        "type": "requisition",
        "department": "Engineering",
        "location": ["San Francisco, CA"],
        "workModel": "hybrid",
        "description": "<p>We are looking for a senior engineer...</p>",
        "shortDescription": "Build great things",
        "status": "active",
        "tags": ["backend"],
        "interviewId": "interview-uuid",
        "screeningQuestionnaireId": "questionnaire-uuid",
        "assignedRecruiterId": "user-uuid",
        "jobFamilyIds": ["family-uuid"],
        "knowledgeBaseIds": ["kb-uuid"],
        "organizationId": "org-uuid",
        "teamId": "team-uuid",
        "createdById": "user-uuid",
        "createdAt": "2024-01-01T00:00:00Z",
        "updatedAt": "2024-01-01T00:00:00Z"
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2024-01-01T00:00:00Z"
    }
  }
  ```
</CodeGroup>

<Callout type="info">
  `jobFamilyIds` and `knowledgeBaseIds` are only returned on the single-get response, not the list endpoint.
</Callout>

## Update Job

Update job fields (whole-body patch — any subset of optional fields).

**Endpoint:** `PATCH /qsi/gather/jobs/{id}`

<ParamField path="id" type="string" required>
  UUID of the job to update
</ParamField>

<ParamField name="title" type="string">
  Updated job title
</ParamField>

<ParamField name="type" type="string">
  Updated job type
</ParamField>

<ParamField name="department" type="string">
  Updated department
</ParamField>

<ParamField name="location" type="string[]">
  Updated location list
</ParamField>

<ParamField name="workModel" type="string">
  Updated work model
</ParamField>

<ParamField name="description" type="string">
  Updated description
</ParamField>

<ParamField name="descriptionJson" type="object">
  Updated structured description
</ParamField>

<ParamField name="requirements" type="string">
  Updated requirements
</ParamField>

<ParamField name="requirementsJson" type="object">
  Updated structured requirements
</ParamField>

<ParamField name="status" type="string">
  Updated status. See the [status limitation callout](#status-limitation) below.
</ParamField>

<ParamField name="tags" type="string[]">
  Updated tags
</ParamField>

<ParamField name="applyAction" type="string">
  Apply button behavior. One of `apply_first`, `interview_first`, `chat_to_apply`.
</ParamField>

<ParamField name="interviewId" type="string">
  UUID of a new interview to attach
</ParamField>

<ParamField name="meetingId" type="string">
  UUID of a new meeting to attach
</ParamField>

<ParamField name="screeningQuestionnaireId" type="string">
  UUID of a new screening questionnaire
</ParamField>

<ParamField name="knowledgeBaseIds" type="string[]">
  Updated knowledge base UUIDs
</ParamField>

<ParamField name="jobFamilyIds" type="string[]">
  Updated job family UUIDs
</ParamField>

<ParamField name="assignedRecruiterId" type="string">
  Updated assigned recruiter UUID
</ParamField>

<Callout type="warning">
  Mutating an ATS-imported job via PATCH returns `409 Conflict`. Use the ATS assign endpoint to manage ATS jobs.
</Callout>

<CodeGroup>
  ```json Request theme={null}
  {
    "title": "Staff Software Engineer",
    "status": "active"
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "job": {
        "id": "job-uuid",
        "title": "Staff Software Engineer",
        "status": "active",
        "updatedAt": "2024-01-01T00:00:00Z"
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2024-01-01T00:00:00Z"
    }
  }
  ```
</CodeGroup>

## Archive Job

Archive (soft-delete) a job. The job remains retrievable with `archivedAt` set.

**Endpoint:** `DELETE /qsi/gather/jobs/{id}`

<ParamField path="id" type="string" required>
  UUID of the job to archive
</ParamField>

<ParamField query="teamId" type="string">
  Team UUID (used for access scoping)
</ParamField>

<ParamField query="userId" type="string">
  UUID of the user performing the archive
</ParamField>

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "job": {
        "id": "job-uuid",
        "archivedAt": "2024-01-01T00:00:00Z"
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2024-01-01T00:00:00Z"
    }
  }
  ```
</CodeGroup>

## Unarchive Job

Restore a previously archived job.

**Endpoint:** `POST /qsi/gather/jobs/{id}/unarchive`

<ParamField path="id" type="string" required>
  UUID of the job to restore
</ParamField>

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "job": {
        "id": "job-uuid",
        "updatedAt": "2024-01-01T00:00:00Z"
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2024-01-01T00:00:00Z"
    }
  }
  ```
</CodeGroup>

## Attach Interview

Attach an interview to a job.

**Endpoint:** `PATCH /qsi/gather/jobs/{id}/interview`

<ParamField path="id" type="string" required>
  UUID of the job
</ParamField>

<ParamField name="interviewId" type="string" required>
  UUID of the interview to attach
</ParamField>

<CodeGroup>
  ```json Request theme={null}
  {
    "interviewId": "interview-uuid"
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "job": {
        "id": "job-uuid",
        "interviewId": "interview-uuid",
        "updatedAt": "2024-01-01T00:00:00Z"
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2024-01-01T00:00:00Z"
    }
  }
  ```
</CodeGroup>

## Attach Screening Questionnaire

Attach a screening questionnaire to a job.

**Endpoint:** `PATCH /qsi/gather/jobs/{id}/screening-questionnaire`

<ParamField path="id" type="string" required>
  UUID of the job
</ParamField>

<ParamField name="screeningQuestionnaireId" type="string" required>
  UUID of the screening questionnaire to attach
</ParamField>

<CodeGroup>
  ```json Request theme={null}
  {
    "screeningQuestionnaireId": "questionnaire-uuid"
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "job": {
        "id": "job-uuid",
        "screeningQuestionnaireId": "questionnaire-uuid",
        "updatedAt": "2024-01-01T00:00:00Z"
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2024-01-01T00:00:00Z"
    }
  }
  ```
</CodeGroup>

## Assign ATS Job

Assign an ATS job to the current team.

**Endpoint:** `POST /qsi/gather/jobs/assign-ats`

<ParamField name="integrationId" type="string" required>
  UUID of the ATS integration
</ParamField>

<ParamField name="externalJobId" type="string" required>
  The job's identifier in the external ATS
</ParamField>

<CodeGroup>
  ```json Request theme={null}
  {
    "integrationId": "integration-uuid",
    "externalJobId": "ats-job-123"
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "job": {
        "id": "job-uuid",
        "title": "Senior Software Engineer",
        "externalId": "ats-job-123",
        "importSource": "greenhouse",
        "teamId": "team-uuid",
        "createdAt": "2024-01-01T00:00:00Z",
        "updatedAt": "2024-01-01T00:00:00Z"
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2024-01-01T00:00:00Z"
    }
  }
  ```
</CodeGroup>

## Unassign Team from Job

Remove the team assignment from a job.

**Endpoint:** `DELETE /qsi/gather/jobs/{id}/team-assignment`

<ParamField path="id" type="string" required>
  UUID of the job
</ParamField>

<ParamField query="teamId" type="string">
  Team UUID to unassign
</ParamField>

<ParamField query="userId" type="string">
  UUID of the user performing the unassignment
</ParamField>

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "success": true
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2024-01-01T00:00:00Z"
    }
  }
  ```
</CodeGroup>

## JobResponse Field Reference

The following fields may be returned on a job object. Fields marked optional (`?`) may be absent when not set.

| Field                      | Type       | Description                                                                                                                                                                                                                             |
| -------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                       | string     | UUID of the job                                                                                                                                                                                                                         |
| `title`                    | string     | Job title                                                                                                                                                                                                                               |
| `type`                     | string     | Job type. One of `requisition`, `pipeline`, `template`                                                                                                                                                                                  |
| `status`                   | string     | Job status. One of `draft`, `pending_approval`, `approved`, `open`, `on_hold`, `closed`, `filled`, `cancelled`, `active`, `paused`, `archived`. Updates are subject to a transition state-machine (an invalid transition returns `400`) |
| `department`               | string?    | Department                                                                                                                                                                                                                              |
| `location`                 | string\[]? | List of location strings                                                                                                                                                                                                                |
| `workModel`                | string?    | Work model (`remote`, `hybrid`, `onsite`)                                                                                                                                                                                               |
| `description`              | string?    | Full job description (HTML or plain text)                                                                                                                                                                                               |
| `descriptionJson`          | object?    | Structured JSON description                                                                                                                                                                                                             |
| `requirements`             | string?    | Requirements text                                                                                                                                                                                                                       |
| `requirementsJson`         | object?    | Structured JSON requirements                                                                                                                                                                                                            |
| `shortDescription`         | string?    | Auto-generated brief summary (read-only — not settable via the API)                                                                                                                                                                     |
| `tags`                     | string\[]? | List of tags                                                                                                                                                                                                                            |
| `applyAction`              | string?    | Apply button behavior. One of `apply_first`, `interview_first`, `chat_to_apply`                                                                                                                                                         |
| `organizationId`           | string     | UUID of the owning organization                                                                                                                                                                                                         |
| `teamId`                   | string     | UUID of the owning team                                                                                                                                                                                                                 |
| `externalId`               | string?    | ID in the external ATS                                                                                                                                                                                                                  |
| `externalSlug`             | string?    | Slug in the external ATS                                                                                                                                                                                                                |
| `importSource`             | string?    | Name of the ATS that imported this job                                                                                                                                                                                                  |
| `interviewId`              | string?    | UUID of the attached interview                                                                                                                                                                                                          |
| `meetingId`                | string?    | UUID of the attached meeting                                                                                                                                                                                                            |
| `screeningQuestionnaireId` | string?    | UUID of the attached screening questionnaire                                                                                                                                                                                            |
| `assignedRecruiterId`      | string?    | UUID of the assigned recruiter                                                                                                                                                                                                          |
| `createdById`              | string     | UUID of the user who created the job                                                                                                                                                                                                    |
| `createdAt`                | string     | ISO-8601 creation timestamp                                                                                                                                                                                                             |
| `updatedAt`                | string     | ISO-8601 last-updated timestamp                                                                                                                                                                                                         |
| `archivedAt`               | string?    | ISO-8601 archive timestamp; omitted from the response when the job is not archived                                                                                                                                                      |
| `ats`                      | object?    | ATS metadata (read-only, see below)                                                                                                                                                                                                     |
| `jobFamilyIds`             | string\[]? | Associated job family UUIDs — **single-get only**                                                                                                                                                                                       |
| `knowledgeBaseIds`         | string\[]? | Associated knowledge base UUIDs — **single-get only**                                                                                                                                                                                   |

### ATS Metadata (`ats` object)

The `ats` field is read-only and populated from the external ATS. It contains up to 8 sub-fields:

| Field            | Type    | Description                                 |
| ---------------- | ------- | ------------------------------------------- |
| `requisitionId`  | string? | ATS requisition identifier                  |
| `hiringManager`  | string? | Name or ID of the hiring manager in the ATS |
| `recruiter`      | string? | Name or ID of the recruiter in the ATS      |
| `headcount`      | number? | Target headcount                            |
| `salaryRange`    | object? | `{ min: number, max: number }`              |
| `employmentType` | string? | Employment type as reported by the ATS      |
| `openDate`       | string? | ISO-8601 date the requisition was opened    |
| `closeDate`      | string? | ISO-8601 date the requisition was closed    |

## Status Limitation

<Callout type="warning" id="status-limitation">
  **Status transitions are validated by a state-machine.** Updating `status` is only permitted along allowed transitions; an invalid transition returns `400 Bad Request` with a message of the form `Invalid status transition from '...' to '...'`. At creation, only `draft`, `pending_approval`, and `active` are accepted — advanced and terminal states (such as `open`, `on_hold`, `closed`, `filled`, `cancelled`, `paused`, `archived`) are reachable only via subsequent update transitions.

  **API keys cannot approve jobs.** On **create**, setting `status: "approved"` always returns `403 Forbidden`. On **update**, transitioning a `pending_approval` job to `approved` returns `403 Forbidden`. Job approvals must be performed through the Humanly dashboard.
</Callout>

## Error Reference

| Status | Code / Cause                                                     | Description                                                                                                                                                                                                                |
| ------ | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Validation error, disallowed field, or invalid status transition | Request body failed validation, a non-settable field (`customFields`, `crm`, `ats`) was included, or a disallowed status transition was attempted (`Invalid status transition from '...' to '...'`)                        |
| `401`  | Missing or invalid API key                                       | The `x-api-key` header is absent, expired, or does not match any team                                                                                                                                                      |
| `403`  | Approval not allowed via API, or acting user not a team member   | On **create**, setting `status: "approved"` always returns 403; on **update**, transitioning a `pending_approval` job to `approved` returns 403. Also returned when the acting `userId` is not a member of the target team |
| `404`  | Not found / wrong team / unknown user                            | The job UUID does not exist or does not belong to the authenticated team, or the acting `userId` does not exist                                                                                                            |
| `409`  | ATS-imported job mutation                                        | Attempted to PATCH an ATS-imported job directly; use the ATS assign endpoint instead                                                                                                                                       |
| `500`  | Internal error                                                   | An unexpected error occurred while processing the request                                                                                                                                                                  |

## Related Resources

<CardGroup cols={2}>
  <Card title="Job Families" icon="layer-group" href="/api-reference/job-families">
    Browse the job family taxonomy associated with your jobs
  </Card>

  <Card title="Interviews" icon="file-text" href="/api-reference/interviews">
    Create and manage interviews to attach to jobs
  </Card>
</CardGroup>
