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

# Sessions

> The session lifecycle, status transitions, and emitted events.

A **session** represents one AI interview — the conversation, the
recording, the transcript, and the scorecard. Sessions are the core unit
of usage and the most-handled object in the API.

## Status lifecycle

```text theme={null}
   created       ┌──────────────┐
   ─────────────►│  scheduled   │  Session row created. AI is provisioned;
                 └─────┬────────┘  candidate has a join URL.
                       │
        candidate joins│  (started_at is stamped here, on room open)
                       ▼
                 ┌──────────────┐  AI is conversing with the candidate.
                 │ in_progress  │  Transcript streams in; authenticity
                 └─────┬────────┘  signals captured live.
                       │
       ┌───────────────┼───────────────┬───────────────┐
       │               │               │               │
 connection drops   completes   recruiter cancels   platform error
       ▼               ▼               ▼               ▼
┌──────────────┐ ┌──────────┐   ┌──────────┐   ┌──────────┐
│ disconnected │ │ completed│   │cancelled │   │  failed  │
└──────────────┘ └──────────┘   └──────────┘   └──────────┘
```

<ResponseField name="scheduled">
  Session row created. AI hasn't joined yet. The candidate can join via the
  `join_url` from the invite at any time.
</ResponseField>

<ResponseField name="in_progress">
  Candidate joined and the AI is actively interviewing. Transcript +
  authenticity signals stream into the session record live.
</ResponseField>

<ResponseField name="completed">
  Interview finished naturally — the AI delivered the closing line and
  ended the call. Scorecard ready within 2 minutes.
</ResponseField>

<ResponseField name="disconnected">
  The candidate's connection dropped mid-interview and they did not return
  within the reconnect grace window. Distinct from `failed`: nothing broke on
  our side — the network did. The partial transcript is retained, and if the
  candidate was in the interview long enough, a scorecard flagged **incomplete**
  is still produced. `error.message` carries the LiveKit disconnect reason
  (`CLIENT_INITIATED`, `SIGNAL_CLOSE`, …) and a connection-quality trail.
</ResponseField>

<ResponseField name="failed">
  A platform or agent error interrupted the session (missing interview
  context, an unrecoverable agent error, a crash). Reserved for problems on
  **our** side — candidate connection drops surface as `disconnected` instead.
  Partial transcript may still be available; no scorecard.

  <Note>
    Before the `disconnected` status existed, connection drops were also
    reported as `failed`. The value is additive: existing integrations that
    only branch on `failed` keep working, but new code should treat
    `disconnected` as the "candidate's network" case and `failed` as the
    "something broke" case.
  </Note>
</ResponseField>

<ResponseField name="cancelled">
  Recruiter cancelled the session via the API or UI before the candidate
  joined. No interview happened.
</ResponseField>

<ResponseField name="no_show">
  The candidate never joined before the scheduled window elapsed. No
  interview happened; no scorecard.
</ResponseField>

## Reconnecting after a drop

A candidate's connection dropping is **not** immediately terminal. When the
candidate's client disconnects mid-interview:

<Steps>
  <Step title="Grace window (3 minutes)">
    The room and AI interviewer stay alive. The candidate's existing
    `join_url` remains valid — reopening it drops them back into the *same*
    session. On rejoin the AI welcomes them back and repeats its last question
    verbatim; it does not restart or re-introduce itself.
  </Step>

  <Step title="Grace window expires">
    If the candidate hasn't returned within the window, the session moves to
    `disconnected`. If they were in the interview long enough, a scorecard
    flagged **incomplete** is still produced from the partial transcript.
  </Step>

  <Step title="Automatic resume email">
    The candidate is emailed a one-click link to resume, which starts a fresh
    attempt — subject to the round's `max_attempts`. Once that budget is spent,
    no further automatic retry is sent.
  </Step>
</Steps>

<Note>
  Drops are also headed off *before* they happen: the candidate lobby runs a
  pre-join mic check plus a browser/network preflight (warning on non-Chrome
  browsers, weak connections, and VPNs), and mid-interview the AI proactively
  tells the candidate when their audio is breaking up so they can fix it before
  the line fully drops.
</Note>

## Status changes emit events

Every state transition fires a webhook. Configure your endpoint to
receive them — see [Webhooks](/en/concepts/webhooks) for the payload shape +
HMAC signing.

| Transition                        | Event fired                                                         |
| --------------------------------- | ------------------------------------------------------------------- |
| Created (via API or UI)           | `session.created`                                                   |
| Candidate joins                   | `session.started`                                                   |
| Completes naturally               | `session.completed`                                                 |
| Connection drops / platform error | `session.failed` *(payload `status` is `disconnected` or `failed`)* |
| Cancelled by recruiter            | `session.cancelled`                                                 |
| Scorecard finalized               | `session.scored` *(fires 30s–2min after `completed`)*               |

<Tip>
  Listen for `session.scored`, not `session.completed`, when you want the
  full rubric scorecard. `session.completed` fires the moment the AI hangs
  up; scoring happens asynchronously and `session.scored` is the signal
  that the scorecard is final.
</Tip>

## Reading a session

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://www.intervyo.ai/api/v1/sessions/e3a1c2d4-..." \
    -H "x-api-key: $INTERVYO_API_KEY"
  ```

  ```ts TypeScript theme={null}
  const res = await fetch(
    `https://www.intervyo.ai/api/v1/sessions/${id}`,
    { headers: { "x-api-key": process.env.INTERVYO_API_KEY! } },
  );
  const { data } = await res.json();
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.get(
      f"https://www.intervyo.ai/api/v1/sessions/{session_id}",
      headers={"x-api-key": os.environ["INTERVYO_API_KEY"]},
  )
  data = res.json()["data"]
  ```
</CodeGroup>

Returns:

```json theme={null}
{
  "data": {
    "id": "e3a1c2d4-...",
    "status": "completed",
    "role_id": "ce1cd564-...",
    "candidate_id": "ba4f2cdd-...",
    "stage": "screen",
    "score": "8.2",
    "passed": true,
    "recommendation": "hire",
    "ai_feedback": "Strong candidate with clear system design...",
    "evaluation_breakdown": [
      { "name": "Technical Depth", "score": 8.5, "feedback": "..." },
      { "name": "Communication",   "score": 7.8, "feedback": "..." }
    ],
    "duration_seconds": 2740,
    "started_at": "2026-06-02T07:48:31Z",
    "ended_at": "2026-06-02T08:21:14Z"
  }
}
```

See [Get Session](/api-reference/sessions/get-sessions-id) for the full schema.

## Re-runs and retries

Each session is immutable once it lands in a terminal state
(`completed` / `failed` / `cancelled`). If you need another interview for
the same candidate — say after a technical failure — create a fresh
session. Sessions for the same `(participant, stage)` are independent
rows; the platform doesn't try to "merge" or "supersede" them.

The Applications panel on each template's detail page automatically
picks the **latest** session per stage when displaying the rolled-up
status, so retries surface immediately.

## Multi-stage progression

Multi-stage templates auto-create a fresh session for the next stage when
a candidate passes a stage. You don't need to manually invoke this — the
session-status mirror trigger handles it.

A candidate's full trajectory through a 3-Round Role ends up as 3
sessions, one per Round, linked through the same `candidate_id`. See
[Roles](/en/concepts/roles) for how Rounds chain together.

## Session vs application

These are two different objects, easy to confuse:

| Object                                    | Purpose                                                                                                                                                                                                   |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Session**                               | One conversation between AI and participant. Holds the transcript, scores, recording.                                                                                                                     |
| **Application** (`template_applications`) | The relationship between a participant and a template. Tracks HR approval, scheduling status, and the latest session. Created by the public apply flow or by manually adding a participant to a template. |

The Applications panel on the template detail page reads from
`template_applications`, but the data shown (scores, transcripts, status)
comes from the latest session joined onto the application. They're paired
1:N — one application can have many sessions over time (retries,
multi-stage progressions).
