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
The case study ID to start a session for. Get this from the id field in GET /v1/case-studies.
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:
| Field | Description |
|---|
duration_limit | Total active time budget in seconds (default: 600) |
elapsed_active_time | Seconds 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
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.
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.
Check attempt limits
Verifies the user hasn’t exhausted their allowed attempts for this case study.
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.
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
| Status | Code | Condition | Details |
|---|
404 | NOT_FOUND | Session or case study not found | — |
409 | SESSION_ACTIVE | User has an active session for this content | details.session_id — the active session ID |
410 | SESSION_CONVERSATION_TTL_EXCEEDED | Reconnect window expired | Grading triggered automatically |
410 | SESSION_RECONNECT_EXCEEDED | Already reconnected 2 times | Grading triggered automatically |
410 | SESSION_TIMER_EXPIRED | Session time limit exceeded on resume | Grading triggered automatically |
410 | SESSION_ENDED or SESSION_<END_REASON> | Session was already ended | Example: SESSION_USER_ENDED |
422 | VALIDATION_ERROR | Maximum attempts reached | details.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
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:
| Trigger | How it happens |
|---|
user_ended | User calls POST /sessions/{id}/end |
timer_expired | 10-minute active timer runs out during conversation |
conversation_ttl_exceeded | User tries to resume after the 1-hour reconnect window has expired |
reconnect_exceeded | User 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
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
Filter sessions by case study ID.
Number of records to skip.
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
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
| Field | Type | Description |
|---|
elapsed_active_time | float | Total active conversation time in seconds |
reconnect_count | integer | Number of times the user has reconnected |
end_reason | string | null | Why the session ended: user_ended, timer_expired, ttl_exceeded, reconnect_exceeded, or null if still active |
duration_limit | integer | Total active time budget in seconds |
Transcript message schema
| Field | Type | Description |
|---|
role | string | "user" (student) or "assistant" (AI agent) |
content | string | The speaker’s text content |
sequence | integer | Message order (1-indexed) |
timestamp | datetime | When the message was captured |
Please deploy