> ## 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.

# Workflows

> Build, apply, and run multi-step job screening workflows

## Overview

Job Workflows are multi-step screening pipelines (e.g., questionnaire → standard interview → AI interviewer → meeting) attached to a job. Steps can also include AI practice interviewer and AI career coach — see [Step Types](#step-types) for the full set. A **workflow template** (`isTemplate: true`) is a reusable definition you apply to one or more jobs or job families; applying it creates an **assignment**. A job's **effective workflow** resolves the template that actually governs it (direct override > directly-applied template > job-family assignment > none). When you invite a candidate to a job, they are **enrolled** in the effective workflow and an **enrollment** with per-step state is created.

<Callout type="info">
  All workflow endpoints require the `jobWorkflow` organization feature. Requests for organizations without it return `403 FORBIDDEN`.
</Callout>

## Create Workflow Template

Create a reusable workflow template with an ordered list of steps.

**Endpoint:** `POST /qsi/gather/workflow-templates`

<ParamField name="name" type="string" required>
  Template name
</ParamField>

<ParamField name="userId" type="string" required>
  UUID of the acting user; recorded as the template creator and validated for org/team membership
</ParamField>

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

<ParamField name="messageGroupId" type="string">
  Optional message group UUID for invite messaging (defaults to the team's default group)
</ParamField>

<ParamField name="steps" type="array" required>
  Ordered steps. Each step: `position` (number), `stepType` (`questionnaire` | `standard_interview` | `ai_interviewer` | `ai_practice_interviewer` | `ai_career_coach` | `meeting`), `artifactId` (UUID of the interview/meeting/questionnaire), optional `scoreThreshold` (0–100), optional `sendDelayMinutes` (number). An `ai_interviewer` step requires an interview with an active scorecard, or the request returns `400`.
</ParamField>

<ParamField name="teamId" type="string">
  Override the team derived from the API key
</ParamField>

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST https://api.prod.qualifi.hr/qsi/gather/workflow-templates \
      -H "x-api-key: ${API_KEY}" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Engineering Screen",
        "userId": "user-456",
        "steps": [
          { "position": 1, "stepType": "questionnaire", "artifactId": "sq-uuid-1" },
          { "position": 2, "stepType": "ai_interviewer", "artifactId": "interview-uuid-1", "scoreThreshold": 70 }
        ]
      }'
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch('https://api.prod.qualifi.hr/qsi/gather/workflow-templates', {
      method: 'POST',
      headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        name: 'Engineering Screen',
        userId: 'user-456',
        steps: [
          { position: 1, stepType: 'questionnaire', artifactId: 'sq-uuid-1' },
          { position: 2, stepType: 'ai_interviewer', artifactId: 'interview-uuid-1', scoreThreshold: 70 }
        ]
      })
    });
    const template = await response.json();
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.post(
        'https://api.prod.qualifi.hr/qsi/gather/workflow-templates',
        headers={'x-api-key': api_key, 'Content-Type': 'application/json'},
        json={
            'name': 'Engineering Screen',
            'userId': 'user-456',
            'steps': [
                {'position': 1, 'stepType': 'questionnaire', 'artifactId': 'sq-uuid-1'},
                {'position': 2, 'stepType': 'ai_interviewer', 'artifactId': 'interview-uuid-1', 'scoreThreshold': 70}
            ]
        }
    )
    template = response.json()
    ```
  </Tab>
</Tabs>

<CodeGroup>
  ```json Request theme={null}
  {
    "name": "Engineering Screen",
    "userId": "user-456",
    "description": "Standard engineering pipeline",
    "steps": [
      { "position": 1, "stepType": "questionnaire", "artifactId": "sq-uuid-1" },
      { "position": 2, "stepType": "ai_interviewer", "artifactId": "interview-uuid-1", "scoreThreshold": 70 }
    ]
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "workflowTemplate": {
        "id": "template-uuid-1",
        "name": "Engineering Screen",
        "description": "Standard engineering pipeline",
        "organizationId": "org-123",
        "teamId": "team-123",
        "messageGroupId": "mg-uuid-1",
        "isTemplate": true,
        "createdById": "user-456",
        "createdAt": "2025-01-17T12:00:00.000Z",
        "updatedAt": "2025-01-17T12:00:00.000Z",
        "steps": [
          {
            "id": "step-uuid-1",
            "position": 1,
            "stepType": "questionnaire",
            "artifactId": "sq-uuid-1",
            "createdAt": "2025-01-17T12:00:00.000Z",
            "updatedAt": "2025-01-17T12:00:00.000Z"
          },
          {
            "id": "step-uuid-2",
            "position": 2,
            "stepType": "ai_interviewer",
            "artifactId": "interview-uuid-1",
            "scoreThreshold": 70,
            "createdAt": "2025-01-17T12:00:00.000Z",
            "updatedAt": "2025-01-17T12:00:00.000Z"
          }
        ]
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2025-01-17T12:00:00.000Z"
    }
  }
  ```
</CodeGroup>

## List Workflow Templates

List all active workflow templates for the resolved team. Not paginated.

**Endpoint:** `GET /qsi/gather/workflow-templates`

<ParamField query="teamId" type="string">
  Override the team derived from the API key
</ParamField>

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "workflowTemplates": [
        {
          "id": "template-uuid-1",
          "name": "Engineering Screen",
          "organizationId": "org-123",
          "teamId": "team-123",
          "messageGroupId": "mg-uuid-1",
          "isTemplate": true,
          "createdById": "user-456",
          "createdAt": "2025-01-17T12:00:00.000Z",
          "updatedAt": "2025-01-17T12:00:00.000Z",
          "steps": [
            {
              "id": "step-uuid-1",
              "position": 1,
              "stepType": "questionnaire",
              "artifactId": "sq-uuid-1",
              "createdAt": "2025-01-17T12:00:00.000Z",
              "updatedAt": "2025-01-17T12:00:00.000Z"
            }
          ]
        }
      ]
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2025-01-17T12:00:00.000Z"
    }
  }
  ```
</CodeGroup>

## Get Workflow Template

Retrieve a single workflow template with its steps and assignments.

**Endpoint:** `GET /qsi/gather/workflow-templates/{id}`

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

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "workflowTemplate": {
        "id": "template-uuid-1",
        "name": "Engineering Screen",
        "description": "Standard engineering pipeline",
        "organizationId": "org-123",
        "teamId": "team-123",
        "messageGroupId": "mg-uuid-1",
        "isTemplate": true,
        "createdById": "user-456",
        "createdAt": "2025-01-17T12:00:00.000Z",
        "updatedAt": "2025-01-17T12:00:00.000Z",
        "steps": [
          {
            "id": "step-uuid-1",
            "position": 1,
            "stepType": "questionnaire",
            "artifactId": "sq-uuid-1",
            "createdAt": "2025-01-17T12:00:00.000Z",
            "updatedAt": "2025-01-17T12:00:00.000Z"
          }
        ],
        "assignments": [
          {
            "id": "assignment-uuid-1",
            "templateId": "template-uuid-1",
            "targetType": "job",
            "targetId": "job-uuid-1",
            "source": "job_template",
            "organizationId": "org-123",
            "teamId": "team-123",
            "initialScoreThreshold": 60,
            "createdAt": "2025-01-17T12:00:00.000Z",
            "updatedAt": "2025-01-17T12:00:00.000Z"
          }
        ]
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2025-01-17T12:00:00.000Z"
    }
  }
  ```
</CodeGroup>

## Update Workflow Template

Update the name, description, messaging, or steps of an existing workflow template.

**Endpoint:** `PATCH /qsi/gather/workflow-templates/{id}`

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

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

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

<ParamField name="messageGroupId" type="string">
  Updated message group UUID for invite messaging
</ParamField>

<ParamField name="steps" type="array">
  Full replacement of the steps list. Each step: `position` (number), `stepType` (`questionnaire` | `standard_interview` | `ai_interviewer` | `ai_practice_interviewer` | `ai_career_coach` | `meeting`), `artifactId`, optional `scoreThreshold` (0–100), optional `sendDelayMinutes` (number). Omitting this field leaves steps unchanged.
</ParamField>

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X PATCH https://api.prod.qualifi.hr/qsi/gather/workflow-templates/template-uuid-1 \
      -H "x-api-key: ${API_KEY}" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Engineering Screen v2",
        "steps": [
          { "position": 1, "stepType": "questionnaire", "artifactId": "sq-uuid-1" },
          { "position": 2, "stepType": "ai_interviewer", "artifactId": "interview-uuid-1", "scoreThreshold": 75 }
        ]
      }'
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch('https://api.prod.qualifi.hr/qsi/gather/workflow-templates/template-uuid-1', {
      method: 'PATCH',
      headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        name: 'Engineering Screen v2',
        steps: [
          { position: 1, stepType: 'questionnaire', artifactId: 'sq-uuid-1' },
          { position: 2, stepType: 'ai_interviewer', artifactId: 'interview-uuid-1', scoreThreshold: 75 }
        ]
      })
    });
    const template = await response.json();
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.patch(
        'https://api.prod.qualifi.hr/qsi/gather/workflow-templates/template-uuid-1',
        headers={'x-api-key': api_key, 'Content-Type': 'application/json'},
        json={
            'name': 'Engineering Screen v2',
            'steps': [
                {'position': 1, 'stepType': 'questionnaire', 'artifactId': 'sq-uuid-1'},
                {'position': 2, 'stepType': 'ai_interviewer', 'artifactId': 'interview-uuid-1', 'scoreThreshold': 75}
            ]
        }
    )
    template = response.json()
    ```
  </Tab>
</Tabs>

<CodeGroup>
  ```json Request theme={null}
  {
    "name": "Engineering Screen v2",
    "steps": [
      { "position": 1, "stepType": "questionnaire", "artifactId": "sq-uuid-1" },
      { "position": 2, "stepType": "ai_interviewer", "artifactId": "interview-uuid-1", "scoreThreshold": 75 }
    ]
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "workflowTemplate": {
        "id": "template-uuid-1",
        "name": "Engineering Screen v2",
        "description": "Standard engineering pipeline",
        "organizationId": "org-123",
        "teamId": "team-123",
        "messageGroupId": "mg-uuid-1",
        "isTemplate": true,
        "createdById": "user-456",
        "createdAt": "2025-01-17T12:00:00.000Z",
        "updatedAt": "2025-01-17T13:00:00.000Z",
        "steps": [
          {
            "id": "step-uuid-1",
            "position": 1,
            "stepType": "questionnaire",
            "artifactId": "sq-uuid-1",
            "createdAt": "2025-01-17T12:00:00.000Z",
            "updatedAt": "2025-01-17T13:00:00.000Z"
          },
          {
            "id": "step-uuid-2",
            "position": 2,
            "stepType": "ai_interviewer",
            "artifactId": "interview-uuid-1",
            "scoreThreshold": 75,
            "createdAt": "2025-01-17T12:00:00.000Z",
            "updatedAt": "2025-01-17T13:00:00.000Z"
          }
        ]
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2025-01-17T13:00:00.000Z"
    }
  }
  ```
</CodeGroup>

## Archive Workflow Template

Archive (soft-delete) a workflow template. Archived templates are no longer returned in list responses.

**Endpoint:** `DELETE /qsi/gather/workflow-templates/{id}`

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

<ParamField query="userId" type="string" required>
  UUID of the acting user
</ParamField>

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "workflowTemplate": {
        "id": "template-uuid-1",
        "name": "Engineering Screen",
        "description": "Standard engineering pipeline",
        "organizationId": "org-123",
        "teamId": "team-123",
        "messageGroupId": "mg-uuid-1",
        "isTemplate": true,
        "createdById": "user-456",
        "createdAt": "2025-01-17T12:00:00.000Z",
        "updatedAt": "2025-01-17T14:00:00.000Z",
        "archivedAt": "2025-01-17T14:00:00.000Z",
        "steps": []
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2025-01-17T14:00:00.000Z"
    }
  }
  ```
</CodeGroup>

## Apply Workflow Template

Apply a workflow template to one or more jobs or job families, creating assignments.

**Endpoint:** `POST /qsi/gather/workflow-templates/{id}/apply`

<ParamField path="id" type="string" required>
  UUID of the workflow template to apply
</ParamField>

<ParamField name="userId" type="string" required>
  UUID of the acting user
</ParamField>

<ParamField name="jobIds" type="array">
  Array of job UUIDs to assign the template to. Provide `jobIds`, `jobFamilyIds`, or both — at least one target is required (a request with neither returns `400`).
</ParamField>

<ParamField name="jobFamilyIds" type="array">
  Array of job family UUIDs to assign the template to. Provide `jobIds`, `jobFamilyIds`, or both — at least one target is required (a request with neither returns `400`).
</ParamField>

<ParamField name="initialScoreThreshold" type="number">
  Optional score threshold (0–100) applied at enrollment to gate candidates before the first step
</ParamField>

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST https://api.prod.qualifi.hr/qsi/gather/workflow-templates/template-uuid-1/apply \
      -H "x-api-key: ${API_KEY}" \
      -H "Content-Type: application/json" \
      -d '{
        "userId": "user-456",
        "jobIds": ["job-uuid-1", "job-uuid-2"],
        "initialScoreThreshold": 60
      }'
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch('https://api.prod.qualifi.hr/qsi/gather/workflow-templates/template-uuid-1/apply', {
      method: 'POST',
      headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        userId: 'user-456',
        jobIds: ['job-uuid-1', 'job-uuid-2'],
        initialScoreThreshold: 60
      })
    });
    const result = await response.json();
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.post(
        'https://api.prod.qualifi.hr/qsi/gather/workflow-templates/template-uuid-1/apply',
        headers={'x-api-key': api_key, 'Content-Type': 'application/json'},
        json={
            'userId': 'user-456',
            'jobIds': ['job-uuid-1', 'job-uuid-2'],
            'initialScoreThreshold': 60
        }
    )
    result = response.json()
    ```
  </Tab>
</Tabs>

<CodeGroup>
  ```json Request theme={null}
  {
    "userId": "user-456",
    "jobIds": ["job-uuid-1", "job-uuid-2"],
    "initialScoreThreshold": 60
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "assignments": [
        {
          "id": "assignment-uuid-1",
          "templateId": "template-uuid-1",
          "targetType": "job",
          "targetId": "job-uuid-1",
          "source": "job_template",
          "organizationId": "org-123",
          "teamId": "team-123",
          "initialScoreThreshold": 60,
          "createdAt": "2025-01-17T12:00:00.000Z",
          "updatedAt": "2025-01-17T12:00:00.000Z"
        },
        {
          "id": "assignment-uuid-2",
          "templateId": "template-uuid-1",
          "targetType": "job",
          "targetId": "job-uuid-2",
          "source": "job_template",
          "organizationId": "org-123",
          "teamId": "team-123",
          "initialScoreThreshold": 60,
          "createdAt": "2025-01-17T12:00:00.000Z",
          "updatedAt": "2025-01-17T12:00:00.000Z"
        }
      ]
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2025-01-17T12:00:00.000Z"
    }
  }
  ```
</CodeGroup>

## Remove Workflow Template Assignment

Remove a workflow template assignment from a job or job family.

**Endpoint:** `DELETE /qsi/gather/workflow-template-assignments/{id}`

<ParamField path="id" type="string" required>
  UUID of the workflow template assignment to remove
</ParamField>

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

## Get Effective Workflow

Resolve the workflow that actually governs a job (direct override > directly-applied template > job-family assignment > none).

**Endpoint:** `GET /qsi/gather/jobs/{jobId}/effective-workflow`

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

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "effectiveWorkflow": {
        "source": "job_family",
        "assignmentId": "assignment-uuid-1",
        "initialScoreThreshold": 60,
        "template": {
          "id": "template-uuid-1",
          "name": "Engineering Screen",
          "organizationId": "org-123",
          "teamId": "team-123",
          "messageGroupId": "mg-uuid-1",
          "isTemplate": true,
          "createdById": "user-456",
          "createdAt": "2025-01-17T12:00:00.000Z",
          "updatedAt": "2025-01-17T12:00:00.000Z",
          "steps": []
        },
        "warnings": []
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2025-01-17T12:00:00.000Z"
    }
  }
  ```
</CodeGroup>

<Callout type="info">
  When no workflow governs the job, the response still returns `200` with `source: "none"` and `template: null` (and no `assignmentId`). Check `source` before reading `template`.

  ```json theme={null}
  {
    "data": {
      "effectiveWorkflow": {
        "source": "none",
        "template": null,
        "warnings": []
      }
    }
  }
  ```
</Callout>

## Set Workflow Override

Override a job's workflow with a specific template, bypassing any job-family assignment.

**Endpoint:** `POST /qsi/gather/jobs/{jobId}/workflow-override`

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

<ParamField name="userId" type="string" required>
  UUID of the acting user
</ParamField>

<ParamField name="templateId" type="string" required>
  UUID of the workflow template to set as the override
</ParamField>

<ParamField name="initialScoreThreshold" type="number">
  Optional score threshold (0–100) applied at enrollment to gate candidates before the first step
</ParamField>

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST https://api.prod.qualifi.hr/qsi/gather/jobs/job-uuid-1/workflow-override \
      -H "x-api-key: ${API_KEY}" \
      -H "Content-Type: application/json" \
      -d '{
        "userId": "user-456",
        "templateId": "template-uuid-1",
        "initialScoreThreshold": 65
      }'
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch('https://api.prod.qualifi.hr/qsi/gather/jobs/job-uuid-1/workflow-override', {
      method: 'POST',
      headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        userId: 'user-456',
        templateId: 'template-uuid-1',
        initialScoreThreshold: 65
      })
    });
    const result = await response.json();
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.post(
        'https://api.prod.qualifi.hr/qsi/gather/jobs/job-uuid-1/workflow-override',
        headers={'x-api-key': api_key, 'Content-Type': 'application/json'},
        json={
            'userId': 'user-456',
            'templateId': 'template-uuid-1',
            'initialScoreThreshold': 65
        }
    )
    result = response.json()
    ```
  </Tab>
</Tabs>

<CodeGroup>
  ```json Request theme={null}
  {
    "userId": "user-456",
    "templateId": "template-uuid-1",
    "initialScoreThreshold": 65
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "effectiveWorkflow": {
        "source": "job_override",
        "assignmentId": "assignment-uuid-3",
        "initialScoreThreshold": 65,
        "template": {
          "id": "template-uuid-1",
          "name": "Engineering Screen",
          "organizationId": "org-123",
          "teamId": "team-123",
          "messageGroupId": "mg-uuid-1",
          "isTemplate": true,
          "createdById": "user-456",
          "createdAt": "2025-01-17T12:00:00.000Z",
          "updatedAt": "2025-01-17T12:00:00.000Z",
          "steps": []
        },
        "warnings": []
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2025-01-17T12:00:00.000Z"
    }
  }
  ```
</CodeGroup>

## Clear Workflow Override

Remove a job's workflow override, reverting to the job-family assignment or no workflow.

**Endpoint:** `DELETE /qsi/gather/jobs/{jobId}/workflow-override`

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

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "effectiveWorkflow": {
        "source": "job_family",
        "assignmentId": "assignment-uuid-1",
        "initialScoreThreshold": 60,
        "template": {
          "id": "template-uuid-1",
          "name": "Engineering Screen",
          "organizationId": "org-123",
          "teamId": "team-123",
          "messageGroupId": "mg-uuid-1",
          "isTemplate": true,
          "createdById": "user-456",
          "createdAt": "2025-01-17T12:00:00.000Z",
          "updatedAt": "2025-01-17T12:00:00.000Z",
          "steps": []
        },
        "warnings": []
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2025-01-17T12:00:00.000Z"
    }
  }
  ```
</CodeGroup>

## Get Workflow Progress

List candidate enrollments and their per-step progress for a job, with pagination.

**Endpoint:** `GET /qsi/gather/jobs/{jobId}/workflow-progress`

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

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

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

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "enrollments": [
        {
          "id": "enrollment-uuid-1",
          "candidateId": "candidate-uuid-1",
          "jobId": "job-uuid-1",
          "templateId": "template-uuid-1",
          "source": "job_override",
          "status": "active",
          "startedAt": "2025-01-17T12:00:00.000Z",
          "completedAt": null,
          "failedAt": null,
          "createdAt": "2025-01-17T12:00:00.000Z",
          "updatedAt": "2025-01-17T12:30:00.000Z",
          "steps": [
            {
              "id": "enrollment-step-uuid-1",
              "position": 1,
              "stepType": "questionnaire",
              "status": "completed",
              "attempt": 1,
              "artifactId": "sq-uuid-1",
              "inviteUrl": "https://app.qualifi.hr/q/token-abc",
              "inviteSentAt": "2025-01-17T12:00:00.000Z",
              "completedAt": "2025-01-17T12:20:00.000Z",
              "failedAt": null,
              "candidateInterviewId": null,
              "candidateMeetingId": null,
              "createdAt": "2025-01-17T12:00:00.000Z",
              "updatedAt": "2025-01-17T12:20:00.000Z"
            },
            {
              "id": "enrollment-step-uuid-2",
              "position": 2,
              "stepType": "ai_interviewer",
              "status": "invited",
              "attempt": 1,
              "artifactId": "interview-uuid-1",
              "inviteUrl": "https://app.qualifi.hr/i/token-def",
              "inviteSentAt": "2025-01-17T12:21:00.000Z",
              "completedAt": null,
              "failedAt": null,
              "candidateInterviewId": "ci-uuid-1",
              "candidateMeetingId": null,
              "createdAt": "2025-01-17T12:00:00.000Z",
              "updatedAt": "2025-01-17T12:21:00.000Z"
            }
          ]
        }
      ],
      "pagination": {
        "page": 1,
        "pageSize": 25,
        "total": 1,
        "totalPages": 1
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2025-01-17T12:30:00.000Z"
    }
  }
  ```
</CodeGroup>

## List Workflow Resources

List all available artifacts (interviews, meetings, message groups, jobs, job families) that can be referenced when building workflow templates.

**Endpoint:** `GET /qsi/gather/workflow-resources`

<ParamField query="teamId" type="string">
  Override the team derived from the API key
</ParamField>

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "workflowResources": {
        "interviews": [
          {
            "id": "interview-uuid-1",
            "title": "Engineering Technical Screen",
            "displayName": "Engineering Technical Screen",
            "stepType": "ai_interviewer",
            "hasActiveScorecard": true,
            "supportsScoreThreshold": true
          }
        ],
        "meetings": [
          {
            "id": "meeting-uuid-1",
            "title": "Hiring Manager Call",
            "displayName": "Hiring Manager Call",
            "stepType": "meeting",
            "hasActiveScorecard": false,
            "supportsScoreThreshold": false
          }
        ],
        "messageGroups": [
          {
            "id": "mg-uuid-1",
            "title": "Default Invite Messages",
            "type": "invite",
            "default": true,
            "organizationId": "org-123",
            "teamId": "team-123",
            "createdAt": "2025-01-17T12:00:00.000Z",
            "updatedAt": "2025-01-17T12:00:00.000Z"
          }
        ],
        "jobs": [
          {
            "id": "job-uuid-1",
            "title": "Senior Software Engineer",
            "type": "requisition",
            "status": "active",
            "department": "Engineering",
            "organizationId": "org-123",
            "teamId": "team-123",
            "createdById": "user-456",
            "createdAt": "2025-01-17T12:00:00.000Z",
            "updatedAt": "2025-01-17T12:00:00.000Z"
          }
        ],
        "jobFamilies": [
          {
            "id": "jf-uuid-1",
            "displayName": "Engineering",
            "teamId": "team-123",
            "organizationId": "org-123",
            "createdById": "user-456",
            "createdAt": "2025-01-17T12:00:00.000Z",
            "updatedAt": "2025-01-17T12:00:00.000Z"
          }
        ]
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2025-01-17T12:00:00.000Z"
    }
  }
  ```
</CodeGroup>

## Invite Candidate to Job

Enroll a candidate in a job's effective workflow and send the first step's invite. This endpoint creates or reuses a candidate record and creates a workflow enrollment.

**Endpoint:** `POST /qsi/gather/jobs/{jobId}/invite-candidate`

<ParamField path="jobId" type="string" required>
  UUID of the job to invite the candidate to
</ParamField>

<ParamField name="userId" type="string" required>
  UUID of the acting user
</ParamField>

Identify the candidate one of two ways: either pass `candidateId` to reuse an existing candidate, or omit it and provide `firstName`, `lastName`, and at least one of `email` or `phone` to create a new one. A request that satisfies neither returns `400`.

<ParamField name="firstName" type="string">
  Candidate's first name. Required when `candidateId` is not provided.
</ParamField>

<ParamField name="lastName" type="string">
  Candidate's last name. Required when `candidateId` is not provided.
</ParamField>

<ParamField name="email" type="string">
  Candidate's email address. When `candidateId` is not provided, supply `email`, `phone`, or both (at least one is required).
</ParamField>

<ParamField name="phone" type="string">
  Candidate's phone number (E.164 format). When `candidateId` is not provided, supply `email`, `phone`, or both (at least one is required).
</ParamField>

<ParamField name="candidateId" type="string">
  UUID of an existing candidate to reuse instead of creating a new one. When provided, `firstName`/`lastName`/`email`/`phone` are not required.
</ParamField>

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST https://api.prod.qualifi.hr/qsi/gather/jobs/job-uuid-1/invite-candidate \
      -H "x-api-key: ${API_KEY}" \
      -H "Content-Type: application/json" \
      -d '{
        "userId": "user-456",
        "firstName": "Jane",
        "lastName": "Smith",
        "email": "jane.smith@example.com",
        "phone": "+15551234567"
      }'
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch('https://api.prod.qualifi.hr/qsi/gather/jobs/job-uuid-1/invite-candidate', {
      method: 'POST',
      headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        userId: 'user-456',
        firstName: 'Jane',
        lastName: 'Smith',
        email: 'jane.smith@example.com',
        phone: '+15551234567'
      })
    });
    const result = await response.json();
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.post(
        'https://api.prod.qualifi.hr/qsi/gather/jobs/job-uuid-1/invite-candidate',
        headers={'x-api-key': api_key, 'Content-Type': 'application/json'},
        json={
            'userId': 'user-456',
            'firstName': 'Jane',
            'lastName': 'Smith',
            'email': 'jane.smith@example.com',
            'phone': '+15551234567'
        }
    )
    result = response.json()
    ```
  </Tab>
</Tabs>

<CodeGroup>
  ```json Request theme={null}
  {
    "userId": "user-456",
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane.smith@example.com",
    "phone": "+15551234567"
  }
  ```

  ```json Response theme={null}
  {
    "data": {
      "enrollment": {
        "id": "enrollment-uuid-1",
        "candidateId": "candidate-uuid-1",
        "jobId": "job-uuid-1",
        "templateId": "template-uuid-1",
        "source": "job_override",
        "status": "active",
        "startedAt": "2025-01-17T12:00:00.000Z",
        "completedAt": null,
        "failedAt": null,
        "createdAt": "2025-01-17T12:00:00.000Z",
        "updatedAt": "2025-01-17T12:00:00.000Z",
        "steps": [
          {
            "id": "enrollment-step-uuid-1",
            "position": 1,
            "stepType": "questionnaire",
            "status": "invited",
            "attempt": 1,
            "artifactId": "sq-uuid-1",
            "inviteUrl": "https://app.qualifi.hr/q/token-abc",
            "inviteSentAt": "2025-01-17T12:00:00.000Z",
            "completedAt": null,
            "failedAt": null,
            "candidateInterviewId": null,
            "candidateMeetingId": null,
            "createdAt": "2025-01-17T12:00:00.000Z",
            "updatedAt": "2025-01-17T12:00:00.000Z"
          }
        ]
      },
      "firstInviteUrl": "https://app.qualifi.hr/q/token-abc"
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2025-01-17T12:00:00.000Z"
    }
  }
  ```
</CodeGroup>

<Callout type="info">
  `firstInviteUrl` is the invite link for the candidate's first workflow step. It is included when an invite URL is available and is omitted otherwise, so treat it as optional.
</Callout>

## Resend Workflow Step Invite

Resend the invite for a specific workflow enrollment step, advancing the `inviteSentAt` timestamp. This endpoint takes no request body.

**Endpoint:** `POST /qsi/gather/workflow-enrollment-steps/{id}/resend-invite`

<ParamField path="id" type="string" required>
  UUID of the workflow enrollment step
</ParamField>

<CodeGroup>
  ```json Response theme={null}
  {
    "data": {
      "enrollmentStep": {
        "id": "enrollment-step-uuid-1",
        "position": 1,
        "stepType": "questionnaire",
        "status": "invited",
        "attempt": 2,
        "artifactId": "sq-uuid-1",
        "inviteUrl": "https://app.qualifi.hr/q/token-abc",
        "inviteSentAt": "2025-01-17T13:00:00.000Z",
        "completedAt": null,
        "failedAt": null,
        "candidateInterviewId": null,
        "candidateMeetingId": null,
        "createdAt": "2025-01-17T12:00:00.000Z",
        "updatedAt": "2025-01-17T13:00:00.000Z"
      }
    },
    "meta": {
      "requestId": "uuid",
      "timestamp": "2025-01-17T13:00:00.000Z"
    }
  }
  ```
</CodeGroup>

## Step Types

Workflow steps are one of: `questionnaire`, `standard_interview`, `ai_interviewer`, `ai_practice_interviewer`, `ai_career_coach`, `meeting`.

## Workflow Sources

Two response fields share the name `source` but describe different things — don't conflate them when cross-referencing an assignment against an effective-workflow response:

* **`assignment.source`** (on assignment objects in the [Get Workflow Template](#get-workflow-template) and [Apply Workflow Template](#apply-workflow-template) responses) records how the assignment was created: `job_template` (template applied directly to a job), `job_family` (template applied to a job family), or `job_override` (a job-level override).
* **`effectiveWorkflow.source`** (on the [Get Effective Workflow](#get-effective-workflow) response) records which rule won when resolving the job, in precedence order: `job_override`, `job_template`, `job_family`, or `none` (no workflow governs the job).

## Enrollment & Step Status

Enrollment `status`: `active`, `completed`, `failed`, `stopped`.
Enrollment-step `status`: `pending`, `invited`, `in_progress`, `completed`, `waiting_for_evaluation`, `passed`, `failed`.

<Callout type="info">
  Clients should be prepared to handle any documented status value. The full valid sets are listed above; additional values may be introduced in future API versions.
</Callout>

## Related Resources

<CardGroup cols={2}>
  <Card title="Jobs" icon="briefcase" href="/api-reference/jobs">
    Create and manage jobs
  </Card>

  <Card title="Candidates" icon="user" href="/api-reference/candidates">
    Create candidates to invite into workflows
  </Card>
</CardGroup>
