Skip to main content
Sessions represent a single voice conversation between a student and an AI agent. Initializing a session returns LiveKit connection details — the frontend uses these to join a real-time voice room. Each session has a 10-minute active timer. Students can reconnect up to 2 times after a disconnect, and they have a 1-hour reconnect window from the last disconnect. The timer picks up where it left off. Grading only triggers on explicit session endings: the student ends the session, the timer expires, or a resume check fails (reconnect window or reconnect limit).

Initialize Session

POST /v1/sessions/initialize
Create a new session or resume an existing one. Returns LiveKit connection details — the frontend uses server_url and participant_token to join the voice room.

Authentication

Requires a valid access token (any role).

Request body

content_id
string
required
The case study ID to start a session for. Get this from the id field in GET /v1/case-studies.
session_id
string
The session ID to resume. Omit to start a new session. Get this from the session.id field in a previous initialize response.

New session

When session_id is omitted, a new session is created. If the user already has an active session for the same case study, the API returns SESSION_ACTIVE instead of silently replacing it — the frontend should prompt the user to resume or start new.
curl -X POST https://mind-be.staging.miva.university/v1/sessions/initialize \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "content_id": "6650e5f6a7b8c9d0e1f2a3b4"
  }'
{
  "success": true,
  "data": {
    "session": {
      "id": "6651a1b2c3d4e5f6a7b8c9d0",
      "user_id": "6650a1b2c3d4e5f6a7b8c9d0",
      "content_type_id": "case_study",
      "content_id": "6650e5f6a7b8c9d0e1f2a3b4",
      "conversation_id": null,
      "status": "pending",
      "is_active": true,
      "start_time": null,
      "end_time": null,
      "last_activity": null,
      "created_at": "2025-06-01T14:00:00Z",
      "elapsed_active_time": 0.0,
      "reconnect_count": 0,
      "end_reason": null,
      "duration_limit": 600
    },
    "server_url": "wss://livekit.mind.miva.university",
    "room_name": "mind-a1b2c3d4e5f6",
    "participant_token": "eyJhbGciOiJIUzI1NiIs...",
    "participant_name": "user",
    "is_resume": false,
    "elapsed_active_time": 0.0,
    "duration_limit": 600,
    "transcript": []
  },
  "message": "Session initialized successfully"
}

Resume session

When session_id is provided, the API validates the session is still resumable (active, within reconnect window, reconnects remaining) and returns a new room with the timer continuing from where it left off.
curl -X POST https://mind-be.staging.miva.university/v1/sessions/initialize \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "content_id": "6650e5f6a7b8c9d0e1f2a3b4",
    "session_id": "6651a1b2c3d4e5f6a7b8c9d0"
  }'
{
  "success": true,
  "data": {
    "session": {
      "id": "6651a1b2c3d4e5f6a7b8c9d0",
      "user_id": "6650a1b2c3d4e5f6a7b8c9d0",
      "content_type_id": "case_study",
      "content_id": "6650e5f6a7b8c9d0e1f2a3b4",
      "conversation_id": null,
      "status": "active",
      "is_active": true,
      "start_time": "2025-06-01T14:00:00Z",
      "end_time": null,
      "last_activity": null,
      "created_at": "2025-06-01T14:00:00Z",
      "elapsed_active_time": 185.0,
      "reconnect_count": 1,
      "end_reason": null,
      "duration_limit": 600
    },
    "server_url": "wss://livekit.mind.miva.university",
    "room_name": "mind-f6e5d4c3b2a1",
    "participant_token": "eyJhbGciOiJIUzI1NiIs...",
    "participant_name": "user",
    "is_resume": true,
    "elapsed_active_time": 185.0,
    "duration_limit": 600,
    "transcript": [
      {
        "role": "user",
        "content": "What about the market analysis section?",
        "sequence": 14,
        "timestamp": "2025-06-01T14:03:00Z"
      },
      {
        "role": "assistant",
        "content": "The market analysis shows three key trends...",
        "sequence": 15,
        "timestamp": "2025-06-01T14:03:10Z"
      }
    ]
  },
  "message": "Session initialized successfully"
}

Session timer

The response includes two fields for building a countdown timer:
FieldDescription
duration_limitTotal active time budget in seconds (default: 600)
elapsed_active_timeSeconds already used across all segments
Remaining time = duration_limit - elapsed_active_time. The backend also enforces this — the agent will say goodbye and disconnect when time runs out. When resuming, the response may also include a transcript array with the saved conversation so the frontend can show context before reconnecting to voice.

What happens internally

1

Check for resume

If session_id is provided, validates the session: ownership, active status, reconnect window (1hr from last disconnect), and reconnect limit (max 2). Increments reconnect count and returns a new room.
2

Check for active session

If no session_id, checks whether the user already has an active session for this case study. Returns SESSION_ACTIVE if one exists.
3

Check attempt limits

Verifies the user hasn’t exhausted their allowed attempts for this case study.
4

Create session record

Creates a new Session document in pending state with timer and reconnect fields. The session becomes active only after the live voice connection actually starts.
5

Generate LiveKit token

Creates a participant token with embedded user attributes (tenant ID, user ID, session ID, case study ID) that the AI agent reads when the session starts.

Error responses

StatusCodeConditionDetails
404NOT_FOUNDSession or case study not found
409SESSION_ACTIVEUser has an active session for this contentdetails.session_id — the active session ID
410SESSION_CONVERSATION_TTL_EXCEEDEDReconnect window expiredGrading triggered automatically
410SESSION_RECONNECT_EXCEEDEDAlready reconnected 2 timesGrading triggered automatically
410SESSION_TIMER_EXPIREDSession time limit exceeded on resumeGrading triggered automatically
410SESSION_ENDED or SESSION_<END_REASON>Session was already endedExample: SESSION_USER_ENDED
422VALIDATION_ERRORMaximum attempts reacheddetails.attempt_count, details.max_attempts

Handling disconnects

When a user disconnects (network drop, tab close, reload), do not call /end. The session stays active on the server. When the user returns, prompt them to resume or end the session. To resume: call initialize with both content_id and session_id. To end and start new: call POST /sessions/{session_id}/end, then initialize without session_id.

End Session

POST /v1/sessions/{session_id}/end
End an active session. This marks the session as ended and triggers the asynchronous grading workflow. Only call this when the user explicitly wants to finish — not on disconnects.

Authentication

Requires a valid access token. Users can only end their own sessions.

Path parameters

session_id
string
required
The session ID.

Example request

curl -X POST https://mind-be.staging.miva.university/v1/sessions/6651a1b2c3d4e5f6a7b8c9d0/end \
  -H "Authorization: Bearer <access_token>"

Response

{
  "success": true,
  "data": {
    "id": "6651a1b2c3d4e5f6a7b8c9d0",
    "user_id": "6650a1b2c3d4e5f6a7b8c9d0",
    "content_type_id": "case_study",
    "content_id": "6650e5f6a7b8c9d0e1f2a3b4",
    "conversation_id": null,
    "is_active": false,
    "start_time": "2025-06-01T14:00:00Z",
    "end_time": "2025-06-01T14:08:30Z",
    "last_activity": null,
    "created_at": "2025-06-01T14:00:00Z",
    "elapsed_active_time": 510.0,
    "reconnect_count": 1,
    "end_reason": "user_ended",
    "duration_limit": 600
  },
  "message": "Session ended successfully"
}

Grading triggers

Grading is triggered automatically in these four scenarios only:
TriggerHow it happens
user_endedUser calls POST /sessions/{id}/end
timer_expired10-minute active timer runs out during conversation
conversation_ttl_exceededUser tries to resume after the 1-hour reconnect window has expired
reconnect_exceededUser tries to resume after using all 2 reconnects
A disconnect alone does not trigger grading. Grade report links and frontend routes use the session ID:
/sessions/{session_id}/report

List Sessions

GET /v1/sessions
List the current user’s sessions. Optionally filter by case study.

Authentication

Requires a valid access token (any role). Returns only the authenticated user’s sessions.

Query parameters

content_id
string
Filter sessions by case study ID.
skip
integer
default:"0"
Number of records to skip.
limit
integer
default:"20"
Max records to return (1–100).

Example request

curl "https://mind-be.staging.miva.university/v1/sessions?content_id=6650e5f6a7b8c9d0e1f2a3b4" \
  -H "Authorization: Bearer <access_token>"

Response

{
  "success": true,
  "data": [
    {
      "id": "6651a1b2c3d4e5f6a7b8c9d0",
      "content_type_id": "case_study",
      "content_id": "6650e5f6a7b8c9d0e1f2a3b4",
      "is_active": false,
      "start_time": "2025-06-01T14:00:00Z",
      "end_time": "2025-06-01T14:08:30Z"
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 20,
  "total_pages": 1,
  "message": null
}

Get Session

GET /v1/sessions/{session_id}
Get a specific session with its full conversation transcript. Each message includes the speaker role, content, sequence number, and timestamp.

Authentication

Requires a valid access token. Users can only view their own sessions.

Path parameters

session_id
string
required
The session ID.

Example request

curl https://mind-be.staging.miva.university/v1/sessions/6651a1b2c3d4e5f6a7b8c9d0 \
  -H "Authorization: Bearer <access_token>"

Response

{
  "success": true,
  "data": {
    "id": "6651a1b2c3d4e5f6a7b8c9d0",
    "user_id": "6650a1b2c3d4e5f6a7b8c9d0",
    "content_type_id": "case_study",
    "content_id": "6650e5f6a7b8c9d0e1f2a3b4",
    "conversation_id": null,
    "is_active": false,
    "start_time": "2025-06-01T14:00:00Z",
    "end_time": "2025-06-01T14:08:30Z",
    "last_activity": null,
    "created_at": "2025-06-01T14:00:00Z",
    "elapsed_active_time": 510.0,
    "reconnect_count": 1,
    "end_reason": "user_ended",
    "duration_limit": 600,
    "messages": [
      {
        "role": "assistant",
        "content": "Hello, I'm Maria. Thank you for seeing me today, doctor.",
        "sequence": 1,
        "timestamp": "2025-06-01T14:00:05Z"
      },
      {
        "role": "user",
        "content": "Hi Maria, welcome. What brings you in today?",
        "sequence": 2,
        "timestamp": "2025-06-01T14:00:15Z"
      },
      {
        "role": "assistant",
        "content": "I've been feeling really tired lately, and I'm going to the bathroom a lot more than usual...",
        "sequence": 3,
        "timestamp": "2025-06-01T14:00:20Z"
      }
    ]
  },
  "message": null
}

Session response fields

FieldTypeDescription
elapsed_active_timefloatTotal active conversation time in seconds
reconnect_countintegerNumber of times the user has reconnected
end_reasonstring | nullWhy the session ended: user_ended, timer_expired, ttl_exceeded, reconnect_exceeded, or null if still active
duration_limitintegerTotal active time budget in seconds

Transcript message schema

FieldTypeDescription
rolestring"user" (student) or "assistant" (AI agent)
contentstringThe speaker’s text content
sequenceintegerMessage order (1-indexed)
timestampdatetimeWhen the message was captured
Please deploy