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

# Edit interview questions

> Read an interview's question list, change it, and send it back — without disturbing the questions you did not touch.

Questions live on an interview as an **ordered list**, and you edit them by reading that list, changing the array, and sending the whole thing back.

| Step                        | Endpoint                     | MCP tool                   |
| --------------------------- | ---------------------------- | -------------------------- |
| Read the current questions  | `GET /job-interview-get`     | `get_interview_definition` |
| Send back the list you want | `POST /job-interview-update` | `update_interview`         |

Both speak the **same question format** that [`POST /job-interview-create-from-array`](/cookbooks/create-an-interview#option-b-from-your-own-questions) accepts, so a definition read from one endpoint can be edited and written back to the other, or used to seed a copy of the interview.

<Note>
  `questions` is **optional** on `job-interview-update`. Omit it and the question list is left completely alone — the endpoint only touches questions when you send the array. Everything else on the endpoint (name, scoring, tags…) works exactly as before.
</Note>

## The loop

<Steps>
  <Step title="Read the interview">
    ```bash theme={null}
    curl "https://cool.jobmojito.com/functions/v1/job-interview-get?position_id=INTERVIEW_UUID" \
      -H "Authorization: Bearer $SUPABASE_JWT"
    ```

    The response's `questions` array is ordered exactly as candidates are asked:

    ```json theme={null}
    {
      "name": "Support Specialist",
      "questions": [
        { "id": "3f2a…", "question": "Tell me about a time you de-escalated an angry customer.", "duration": 120, "is_multiple_choice": false, "…": null },
        { "id": "9c1b…", "question": "How do you prioritize a full support queue?", "duration": 90, "…": null }
      ]
    }
    ```

    Each `id` is the question's real identifier. **Keep the ids of questions you did not mean to change** — that is how the update recognises them.
  </Step>

  <Step title="Edit the array">
    Work on the array you just read:

    * **Change a question** — edit its text (or any field) and keep its `id`.
    * **Remove one** — delete the entry.
    * **Add one** — append an entry with no `id`.
    * **Reorder** — move entries around.
  </Step>

  <Step title="Send the whole list back">
    ```bash theme={null}
    curl -X POST https://cool.jobmojito.com/functions/v1/job-interview-update \
      -H "Authorization: Bearer $SUPABASE_JWT" \
      -H "Content-Type: application/json" \
      -d '{
        "position_id": "INTERVIEW_UUID",
        "questions": [
          { "id": "9c1b…", "question": "How do you prioritize a full support queue?", "duration": 90 },
          { "id": "3f2a…", "question": "Tell me about a time you turned around an angry customer." },
          { "question": "Which ticketing systems have you used?", "duration": 60 }
        ]
      }'
    ```

    Send the **complete list you want the interview to end up with** — there is no way to change a single question on its own. An empty array is rejected: an interview needs at least one question.
  </Step>

  <Step title="Check what it decided">
    The response tells you exactly what happened, so you never have to guess. `questions_diff` is present only when you sent `questions` — it is absent from the response otherwise:

    ```json theme={null}
    {
      "position_id": "INTERVIEW_UUID",
      "updated_fields": ["questions"],
      "questions_diff": {
        "kept": ["9c1b…"],
        "replaced": [{ "from": "3f2a…", "to": "7d4e…" }],
        "added": ["b820…"],
        "removed": [],
        "question_ids": ["…full ordered step list…"],
        "reactivated": true
      }
    }
    ```
  </Step>
</Steps>

## How your array is matched

The array is applied as a **diff, not a replace**. Each entry is matched against what is stored, trying in order:

1. **`external_id`** — your own stable identifier, when both sides carry one. This lets an ATS send its own array without ever storing our ids.
2. **`id`** — the question id `job-interview-get` returned.
3. **Identical content** — so an array regenerated without ids still recognises the questions that did not actually change.

Then, per entry:

| Situation                             | What happens                                                           |
| ------------------------------------- | ---------------------------------------------------------------------- |
| Matches a question, nothing differs   | **Kept** — the question record is untouched                            |
| Matches a question, something differs | **Replaced** — the old one is unlinked, a new one created in its place |
| Matches nothing                       | **Added** — a new question is created                                  |
| A stored question no entry matched    | **Removed** — unlinked from this interview                             |

<Info>
  **Resending the array you just read changes nothing.** If the diff finds nothing to do, no write happens at all — `updated_fields` comes back without `questions`, and the interview's `updated_at` is not even bumped. That makes the read → edit → write loop safe to run repeatedly, and safe for an agent to run when it is not sure whether anything changed.
</Info>

## Why editing replaces a question

Questions are **shared records**: the same question can be used by more than one interview. So this endpoint never edits one in place — that would silently rewrite the question inside every other interview using it. Instead it unlinks the old record and creates a new one, exactly like **Duplicate & Edit** in the admin.

Two consequences worth knowing:

* **Nothing is ever deleted.** Removing a question only unlinks it from this interview; the record itself survives, and so do the results that reference it.
* **Keeping a question keeps everything attached to it** — its answer rules, its rendered avatar video, and the fields this API does not expose (coach keywords, categories, and so on). That is the real reason to send back the `id`s: a question you keep costs nothing and loses nothing, while a replaced one starts fresh.

<Tip>
  Fields you leave out of an edited entry are **carried over from the question you are replacing** — so `{ "id": "3f2a…", "question": "New wording" }` keeps the old duration, label and `external_id` rather than clearing them.
</Tip>

## Editing a live interview

| Interview status | Template type                                  | Can you change questions?                |
| ---------------- | ---------------------------------------------- | ---------------------------------------- |
| `draft`          | any                                            | ✅ Yes                                    |
| `active`         | `interactive_heygen`, `interactive_elevenlabs` | ✅ Yes — re-published automatically       |
| `active`         | `offline_heygen` (and legacy `offline_*`)      | ❌ **422** — set it back to `draft` first |

On the interactive templates there is no video to render, so the interview is re-published for you and the new questions go live immediately (`questions_diff.reactivated` is `true`). The offline templates pre-render a video per question, so a live one has to go back to `draft` before its questions can change:

```bash theme={null}
# 1. unpublish and edit in one call — status:"draft" is applied before the questions
curl -X POST …/job-interview-update \
  -d '{"position_id":"…", "status":"draft", "questions":[…]}'

# 2. publish again, which renders the video for each new question
curl -X POST …/job-interview-update \
  -d '{"position_id":"…", "status":"active"}'
```

<Warning>
  Changing the questions of an interview candidates have already completed does not rewrite their results — those still refer to the questions that were asked at the time. If you need a clean comparison across candidates, create a new interview instead of editing a live one.
</Warning>

## What this does not change

* **The welcome and thank-you messages, and the instructional-video screen.** They are stored as steps rather than questions, are not part of the `questions` array, and are left in place. Set them at creation with `welcome_message` and `thank_you_message`.
* **The language.** `mojito_language_code` is fixed after creation — the existing questions and any rendered videos are already in it. Create a new interview to change language. (An individual question may still override it.)
* **Multi-stage positions.** A position has no question list of its own; fetch and update its interview stages individually.

## Re-deriving the scoring rubric

The interview-level `candidate_expectations_json` rubric is derived from the questions, so a big change to the list can leave it out of date. Pass `regenerate_candidate_expectations: true` alongside `questions` to re-derive it from the resulting list, the way creation does:

```json theme={null}
{
  "position_id": "INTERVIEW_UUID",
  "questions": [ "…" ],
  "regenerate_candidate_expectations": true
}
```

It applies only to `type: "interview"`, and sending your own `candidate_expectations_json` in the same call wins over the regeneration.

## Next steps

<CardGroup cols={2}>
  <Card title="Create an interview" icon="plus" href="/cookbooks/create-an-interview">
    Start from a job description, your own questions, or a role-play persona.
  </Card>

  <Card title="Invite candidates" icon="user-plus" href="/cookbooks/invite-candidates">
    Turn your interview into links or email invitations.
  </Card>
</CardGroup>
