---
title: "Generate candidate-facing feedback for a completed interview"
method: POST
path: "/api/public/v1/interviews/{interview_id}/candidate-feedback"
tags: ["Public API v1 — Candidate Feedback", "Public API v1 — Candidate Feedback"]
---

# Generate candidate-facing feedback for a completed interview

`POST /api/public/v1/interviews/{interview_id}/candidate-feedback`

## Request

- Base URL: `https://api.thebetterhire.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.thebetterhire.ai/api/public/v1/interviews/{interview_id}/candidate-feedback`
- Auth: HTTP bearer

Turns the interview's scorecard into feedback written for the candidate: the one gap to the next level, the habit that cost the most (with a pointer to the moment), the best moment (with a pointer), one line per competency, and, when `self_read` is given, what the interviewer actually heard.

From contract 2.0, `feedback.structure` lists the candidate's stories (a main question plus the interviewer's follow-ups): each framework part `present`, `when_asked` (given only after the interviewer asked) or `missing`, with `ref.first_turn` / `ref.last_turn` indexing `transcript.segments[]` on `GET /interviews/{id}`. `structure.status` is `ready`, `not_applicable` (skill rounds) or `unavailable` (the rest of the feedback still stands). From 2.1, `structure.openings` reads the introduction and the "why this role" answer: `seconds_spoken` (the candidate's own turns) and `focus` (`on_role`, `partly` or `wandered`), with a note.

Every pointer is `{competency, evidence}`: the competency `name` and an index into that competency's `evidence[]` on `GET /interviews/{id}`, so you can play the seconds behind it. No numbers ever appear in the text.

Works for every completed interview kind. Generated once and stored: a second call returns the same object without a charge unless `regenerate: true`. `status: unavailable` with `unavailable_reason` is a normal, unbilled answer (`not_scored` when the transcript-quality gate stripped scoring, `generation_failed` after three rejected attempts). 409 `FEEDBACK_IN_PROGRESS` with `Retry-After` while another request is generating feedback for the same interview.

With `"async": true`, 202 `{status: "generating"}` instead of waiting; the `candidate_feedback.completed` webhook (`data.status`, `data.structure_status`) fires when it is stored, and GET returns it.

Billed once per generation as the `feedback` action, above the per-candidate cap.

## Path parameters

- `interview_id` string, required

## Request body

- CandidateFeedbackRequest
  - `self_read` string, nullable: The candidate's own read of how it went, written before seeing any result. When present, the feedback's `heard` sets what the interviewer actually heard against it.
  - `regenerate` boolean: Discard the stored feedback and generate again. Billed again. Without it a second call returns the stored object for free.
  - `async` boolean: Answer 202 at once and generate in the background, instead of holding the request open for the generation (typically 20-60 seconds). When it is done, the `candidate_feedback.completed` webhook fires; or poll GET (409 `FEEDBACK_IN_PROGRESS` while it runs). A stored result, the sandbox and every error still answer at once.

## Response `200`

Successful Response

- CandidateFeedbackResult: The stored and returned object.
  - `interview_id` string, required
  - `status` 'ready' | 'unavailable', required
  - `generated_at` string, nullable
  - `unavailable_reason` 'not_scored' | 'generation_failed', nullable
  - `feedback` CandidateFeedback: The stored/returned pieces: the draft plus the reserved field and, from contract 2.0, the story list.
    - `gap` string, required
    - `habit` HabitFeedback, required
      - `title` string, required
      - `explanation` string, required
      - `ref` EvidenceRef, required: Points at one quote on the public scorecard.
        - `competency` string, required: Competency name exactly as on the scorecard.
        - `evidence` integer, required: Index into that competency's evidence[].
    - `best_moment` BestMoment, required
      - `title` string, required
      - `ref` EvidenceRef, required: Points at one quote on the public scorecard.
        - `competency` string, required: Competency name exactly as on the scorecard.
        - `evidence` integer, required: Index into that competency's evidence[].
    - `competency_lines` CompetencyLine[], required
      - `name` string, required
      - `line` string, required
    - `heard` string, nullable
    - `rewrite` string, nullable
    - `structure` Structure: The story list: each story's framework parts, told unprompted (`present`), only after the interviewer asked (`when_asked`), or never (`missing`).
      - `status` 'ready' | 'not_applicable' | 'unavailable', required
      - `reason` 'skill_round' | 'generation_failed', nullable
      - `style` string, nullable
      - `parts` string[]
      - `stories` Story[]
        - `question` string, required
        - `competency` string, nullable
        - `ref` TurnRef, required: A span of `GET /interviews/{id}` → `transcript.segments[]`.
          - `first_turn` integer, required
          - `last_turn` integer, required
          - `start_seconds` number, required
          - `end_seconds` number, required
        - `present` string[], required
        - `when_asked` string[], required
        - `missing` string[], required
        - `note` string, required
      - `pattern` string, nullable
      - `openings` Opening[]
        - `kind` 'introduction' | 'motivation', required
        - `question` string, required
        - `ref` TurnRef, required: A span of `GET /interviews/{id}` → `transcript.segments[]`.
          - `first_turn` integer, required
          - `last_turn` integer, required
          - `start_seconds` number, required
          - `end_seconds` number, required
        - `seconds_spoken` integer, required
        - `focus` 'on_role' | 'partly' | 'wandered', required
        - `note` string, required
      - `transcript_fingerprint` string, nullable
  - `contract_version` string
  - `scorecard_fingerprint` string, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/thebetterhire/apis/interanalysis-b2b-api.md) · [All operations](https://skmtc.dev/thebetterhire/apis/interanalysis-b2b-api/llms.txt) · [OpenAPI document](https://skmtc.dev/thebetterhire/apis/interanalysis-b2b-api/revisions/c8ad03e900ea?raw)
