sar / docs.txt
Mafia2008's picture
feat: Add persistent session export and slide URL generation for late-joining students
01d5160
═══════════════════════════════════════════════════════════════════════════════
SLIDEMAP API DOCUMENTATION
HF Space: https://Mafia2008-sar.hf.space
API Docs UI: https://Mafia2008-sar.hf.space/api-docs
CORS: Enabled (any frontend can call these APIs)
Protocol: REST (JSON) + SSE (Server-Sent Events)
═══════════════════════════════════════════════════════════════════════════════
BASE URL
────────
https://Mafia2008-sar.hf.space
HOW IT WORKS (Flow)
───────────────────
1. Teacher creates a session β†’ gets a 4-digit room code
2. Students look up session by room code β†’ get sessionId
3. Both Teacher & Students subscribe to /api/events (SSE stream)
4. Teacher uploads slides β†’ students see them live via SSE
5. Teacher creates polls β†’ students vote via API
6. Students submit doubts β†’ teacher resolves via API
7. Teacher ends session β†’ all students notified via SSE
═══════════════════════════════════════════════════════════════════════════════
1. SESSION API
POST /api/session
═══════════════════════════════════════════════════════════════════════════════
── 1.1 Create Session (Teacher) ─────────────────────────────────────────────
POST /api/session
Content-Type: application/json
Request Body:
{
"action": "create", // Required
"teacherId": "string", // Required - unique teacher ID
"teacherName": "string", // Required - display name
"sessionName": "string", // Required - class/session name
"description": "string" // Optional
}
Response:
{
"success": true,
"data": {
"id": "uuid-string",
"roomCode": "4821", // 4-digit code for students
"name": "Physics Ch5",
"teacherId": "teacher-001",
"teacherName": "Mr. Sharma",
"isActive": true,
"currentSlide": 0,
"totalSlides": 0,
"studentCount": 0,
"createdAt": "2026-04-14T16:00:00.000Z"
}
}
Example (JS):
fetch('https://Mafia2008-sar.hf.space/api/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'create',
teacherId: 'teacher-001',
teacherName: 'Mr. Sharma',
sessionName: 'Physics – Chapter 5'
})
});
── 1.2 Lookup Session by Room Code ──────────────────────────────────────────
POST /api/session
Content-Type: application/json
Request Body:
{
"action": "lookup", // Required
"roomCode": "4821" // Required - 4-digit room code
}
Response:
{
"success": true,
"data": { "id": "uuid", "roomCode": "4821", ... }
}
Example (JS):
fetch('https://Mafia2008-sar.hf.space/api/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'lookup', roomCode: '4821' })
});
── 1.3 Join Session (Student) ───────────────────────────────────────────────
POST /api/session
Content-Type: application/json
Request Body:
{
"action": "join", // Required
"sessionId": "4821", // Required - sessionId OR room code
"userId": "student-xyz", // Required - unique student ID
"userName": "Rahul Kumar" // Required - display name
}
Response:
{
"success": true,
"data": {
"user": { "id": "...", "name": "Rahul Kumar", "role": "student" },
"session": { ... },
"currentSlide": 0,
"activePoll": null,
"whiteboardStrokes": [],
"reactions": {}
}
}
Example (JS):
fetch('https://Mafia2008-sar.hf.space/api/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'join',
sessionId: '4821', // room code accepted
userId: 'student-xyz',
userName: 'Rahul Kumar'
})
});
── 1.4 End Session (Teacher) ────────────────────────────────────────────────
POST /api/session
Content-Type: application/json
Request Body:
{
"action": "end", // Required
"sessionId": "uuid" // Required
}
Response:
{ "success": true, "message": "Session ended successfully" }
SSE broadcast: session_ended β†’ all connected clients
── 1.5 List All Active Sessions ─────────────────────────────────────────────
POST /api/session
Content-Type: application/json
Request Body:
{ "action": "list" }
Response:
{
"success": true,
"data": [ { "id": "...", "roomCode": "4821", ... } ]
}
── 1.6 Get Session State ────────────────────────────────────────────────────
POST /api/session
Content-Type: application/json
Request Body:
{
"action": "get", // Required
"sessionId": "uuid" // Required
}
Response:
{
"success": true,
"data": {
"session": { ... },
"currentSlide": 2,
"activePoll": { ... } | null,
"doubts": [ ... ],
"recentChats": [ ... ],
"whiteboardStrokes": [ ... ],
"reactions": { "πŸ‘": 3, "❀️": 1 },
"onlineUsers": [ ... ]
}
}
═══════════════════════════════════════════════════════════════════════════════
2. EVENTS API (SSE - Server-Sent Events)
GET /api/events
═══════════════════════════════════════════════════════════════════════════════
Opens a persistent SSE stream. Connect immediately after joining a session.
Both teacher AND students should connect to this endpoint.
URL:
GET /api/events?sessionId=UUID&userId=YOUR_USER_ID
Query Parameters:
sessionId string Required The session ID
userId string Required Your unique user ID
Response Headers:
Content-Type: text/event-stream
Cache-Control: no-cache
Access-Control-Allow-Origin: *
── SSE Events Table ──────────────────────────────────────────────────────
Event Name | Triggered When | Data Fields
──────────────────|────────────────────────────────────|────────────────────
connected | Stream opens | sessionId, userId, full session state
slide_change | Teacher changes/uploads slide | slideNumber, imageBase64, totalSlides, timestamp
new_poll | Teacher creates a poll | id, question, options[], isActive, ...
poll_update | A student votes | pollId, results{}, totalVotes, timestamp
poll_ended | Teacher ends poll | pollId, results{}, totalVotes, correctAnswer
correct_answer | Teacher reveals correct answer | pollId, correctOptionIds[], correctAnswer
new_doubt | Student submits a doubt | id, studentName, question, slideNumber, status
doubt_solved | Teacher resolves a doubt | id, status, teacherResponse, annotatedImage
doubt_updated | Doubt status changes | id, status
student_joined | A new student joins | userId, userName, studentCount
student_left | A student disconnects | userId, userName, studentCount
session_ended | Teacher ends the session | sessionId, endedAt
chat_message | Someone sends a chat message | id, userId, userName, message, isTeacher
reaction | Someone reacts | reactions{}, lastReaction
state_sync | Reconnect (sends full state again) | full session state
Example (JS):
const es = new EventSource(
'https://Mafia2008-sar.hf.space/api/events?sessionId=UUID&userId=STUDENT_ID'
);
// Connected - get initial state
es.addEventListener('connected', e => {
const state = JSON.parse(e.data);
console.log('Session state:', state);
});
// Slide changed by teacher
es.addEventListener('slide_change', e => {
const { slideNumber, imageBase64, totalSlides } = JSON.parse(e.data);
document.getElementById('slide-img').src = imageBase64;
});
// New poll created
es.addEventListener('new_poll', e => {
const poll = JSON.parse(e.data);
showPollUI(poll);
});
// Live vote update
es.addEventListener('poll_update', e => {
const { pollId, results, totalVotes } = JSON.parse(e.data);
updatePollResults(results);
});
// Poll ended
es.addEventListener('poll_ended', e => {
const { pollId, results, correctAnswer } = JSON.parse(e.data);
showFinalResults(results, correctAnswer);
});
// Teacher reveals correct answer
es.addEventListener('correct_answer', e => {
const { pollId, correctOptionIds } = JSON.parse(e.data);
highlightCorrectAnswer(correctOptionIds);
});
// Student submitted a doubt (Teacher listens to this)
es.addEventListener('new_doubt', e => {
const doubt = JSON.parse(e.data);
addDoubtCard(doubt);
});
// Teacher resolved a doubt (Student listens to this)
es.addEventListener('doubt_solved', e => {
const doubt = JSON.parse(e.data);
updateDoubtStatus(doubt);
});
// Session ended
es.addEventListener('session_ended', e => {
alert('Session has ended');
es.close();
});
// Heartbeat (keep-alive, every 15s) - no action needed
es.onerror = err => {
console.error('SSE error:', err);
// Reconnect logic here
};
═══════════════════════════════════════════════════════════════════════════════
3. SLIDE API
POST /api/slide
═══════════════════════════════════════════════════════════════════════════════
── 3.1 Upload & Broadcast Slide (Teacher β†’ Students) ────────────────────────
POST /api/slide
Content-Type: application/json
Request Body:
{
"action": "upload", // Required
"sessionId": "uuid", // Required
"slideImage": "data:image/png;base64,iVBOR...", // Required - base64 image
"slideNumber": 3 // Optional - replaces slide at index; omit to append
}
Notes:
- slideImage can be a data URL ("data:image/png;base64,...") or raw base64
- Automatically broadcasts slide_change SSE to ALL connected students
- Students receive the imageBase64 directly β€” no separate fetch needed
Response:
{
"success": true,
"data": { "slideNumber": 3, "totalSlides": 4 }
}
Example (Electron / SlideMap app):
const canvas = document.getElementById('myCanvas');
const base64 = canvas.toDataURL('image/png');
fetch('https://Mafia2008-sar.hf.space/api/slide', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'upload',
sessionId: SESSION_ID,
slideImage: base64,
slideNumber: currentSlideIndex // optional
})
});
── 3.2 Go to Slide (Teacher) ────────────────────────────────────────────────
POST /api/slide
Content-Type: application/json
Request Body:
{
"action": "goto", // Required
"sessionId": "uuid", // Required
"slideNumber": 2 // Required - 0-indexed slide number
}
Response:
{ "success": true, "data": { "slideNumber": 2, "totalSlides": 5 } }
── 3.3 Next Slide ───────────────────────────────────────────────────────────
POST /api/slide
Body: { "action": "next", "sessionId": "uuid" }
Response:
{ "success": true, "data": { "slideNumber": 3, "totalSlides": 5 } }
── 3.4 Previous Slide ───────────────────────────────────────────────────────
POST /api/slide
Body: { "action": "prev", "sessionId": "uuid" }
Response:
{ "success": true, "data": { "slideNumber": 1, "totalSlides": 5 } }
═══════════════════════════════════════════════════════════════════════════════
4. POLL API
POST /api/poll (create/vote/end/setCorrectAnswers)
GET /api/poll (fetch polls)
═══════════════════════════════════════════════════════════════════════════════
── 4.1 Create Poll (Teacher) ────────────────────────────────────────────────
POST /api/poll
Content-Type: application/json
Request Body:
{
"action": "create", // Required
"sessionId": "uuid", // Required
"question": "string", // Required
"options": ["A","B","C"], // Required - min 2 items
"type": "single", // Optional - "single"|"multiple" (default: "single")
"correctAnswer": "A", // Optional - marks correct option
"duration": 0 // Optional - auto-end after N seconds (0 = manual)
}
Notes:
- Any existing active poll is auto-ended before creating a new one
- Broadcasts new_poll SSE to ALL students immediately
Response:
{
"success": true,
"data": {
"id": "poll-uuid",
"sessionId": "uuid",
"question": "What is Newton's 2nd Law?",
"options": [
{ "id": "option_0", "text": "F=ma", "isCorrect": true, "votes": 0 },
{ "id": "option_1", "text": "E=mcΒ²", "isCorrect": false, "votes": 0 }
],
"type": "single",
"isActive": true,
"results": { "F=ma": 0, "E=mcΒ²": 0 },
"totalVotes": 0,
"votedStudents": [],
"createdAt": "2026-04-14T16:00:00.000Z"
}
}
Example (JS):
fetch('https://Mafia2008-sar.hf.space/api/poll', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'create',
sessionId: SESSION_ID,
question: "What is Newton's 2nd Law?",
options: ['F=ma', 'E=mcΒ²', 'PV=nRT', 'V=IR'],
correctAnswer: 'F=ma',
duration: 0
})
});
── 4.2 Vote on Poll (Student) ───────────────────────────────────────────────
POST /api/poll
Content-Type: application/json
Request Body:
{
"action": "vote", // Required
"sessionId": "uuid", // Required
"pollId": "poll-uuid", // Required
"option": "F=ma", // Required - the option text student selected
"studentId": "student-xyz" // Required - student unique ID
}
Notes:
- Each student can vote only ONCE per poll
- Broadcasts poll_update SSE to ALL clients with live vote counts
Response:
{
"success": true,
"data": {
"pollId": "poll-uuid",
"results": { "F=ma": 5, "E=mcΒ²": 2 },
"totalVotes": 7,
"voted": true
}
}
Example (JS):
fetch('https://Mafia2008-sar.hf.space/api/poll', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'vote',
sessionId: SESSION_ID,
pollId: POLL_ID,
option: 'F=ma',
studentId: MY_STUDENT_ID
})
});
── 4.3 End Poll (Teacher) ───────────────────────────────────────────────────
POST /api/poll
Content-Type: application/json
Request Body:
{
"action": "end", // Required
"sessionId": "uuid", // Required
"pollId": "poll-uuid" // Required
}
Notes:
- Broadcasts poll_ended SSE with final results + correctAnswer
Response:
{ "success": true, "message": "Poll ended successfully" }
── 4.4 Reveal Correct Answer (Teacher) ──────────────────────────────────────
POST /api/poll
Content-Type: application/json
Request Body:
{
"action": "setCorrectAnswers", // Required
"sessionId": "uuid", // Required
"pollId": "poll-uuid", // Required
"correctAnswer": "F=ma", // Optional - option text
"correctOptionIds": ["option_0"] // Optional - option id array
}
Notes:
- Broadcasts correct_answer SSE so students can highlight the correct option
- Use this AFTER ending the poll to reveal the answer
Response:
{ "success": true, "message": "Correct answers broadcast to students" }
── 4.5 Get All Polls ────────────────────────────────────────────────────────
GET /api/poll?sessionId=UUID
Response:
{
"success": true,
"data": {
"polls": [ { ... }, { ... } ],
"activePoll": { ... } | null,
"totalPolls": 3
}
}
═══════════════════════════════════════════════════════════════════════════════
5. DOUBT API
POST /api/doubt (submit/resolve/update)
GET /api/doubt (fetch doubts)
═══════════════════════════════════════════════════════════════════════════════
── 5.1 Submit Doubt (Student β†’ Teacher) ─────────────────────────────────────
POST /api/doubt
Content-Type: application/json
Request Body:
{
"action": "create", // Required
"sessionId": "uuid", // Required
"studentId": "student-xyz", // Required
"studentName": "Rahul Kumar", // Required
"question": "string", // Required - the doubt text
"image": "data:image/png;base64,...", // Optional
"slideNumber": 3 // Optional - slide where doubt occurred
}
Notes:
- Broadcasts new_doubt SSE to the teacher (and all connected clients)
- slideNumber auto-filled from current slide if not provided
Response:
{
"success": true,
"data": {
"id": "doubt-uuid",
"sessionId": "uuid",
"studentId": "student-xyz",
"studentName": "Rahul Kumar",
"question": "Why F=ma and not F=mv?",
"slideNumber": 3,
"status": "pending",
"createdAt": "2026-04-14T16:00:00.000Z"
}
}
Example (JS):
fetch('https://Mafia2008-sar.hf.space/api/doubt', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'create',
sessionId: SESSION_ID,
studentId: MY_STUDENT_ID,
studentName: MY_NAME,
question: "I don't understand why F=ma and not F=mv",
slideNumber: 3
})
});
── 5.2 Resolve Doubt (Teacher) ──────────────────────────────────────────────
POST /api/doubt
Content-Type: application/json
Request Body:
{
"action": "solve", // Required
"sessionId": "uuid", // Required
"doubtId": "doubt-uuid", // Required
"teacherResponse": "string", // Optional - text explanation
"annotatedImage": "data:image/..." // Optional - annotated image
}
Notes:
- Broadcasts doubt_solved SSE so the student sees the resolution
Response:
{
"success": true,
"data": {
"id": "doubt-uuid",
"status": "solved",
"teacherResponse": "Because acceleration = rate of change of velocity...",
"solvedAt": "2026-04-14T16:05:00.000Z"
}
}
── 5.3 Update Doubt Status (Teacher) ────────────────────────────────────────
POST /api/doubt
Content-Type: application/json
Request Body:
{
"action": "update", // Required
"sessionId": "uuid", // Required
"doubtId": "doubt-uuid", // Required
"status": "in_progress" // Required: "pending"|"in_progress"|"solved"
}
Notes:
- Broadcasts doubt_updated SSE to all clients
Response:
{ "success": true, "data": { "id": "...", "status": "in_progress" } }
── 5.4 Get All Doubts (Teacher) ─────────────────────────────────────────────
GET /api/doubt?sessionId=UUID
Response:
{
"success": true,
"data": {
"doubts": [ { ... }, { ... } ],
"summary": {
"total": 7,
"pending": 2,
"inProgress": 1,
"solved": 4
}
}
}
═══════════════════════════════════════════════════════════════════════════════
6. CHAT API
POST /api/chat (send message)
GET /api/chat (get history)
═══════════════════════════════════════════════════════════════════════════════
── 6.1 Send Chat Message ────────────────────────────────────────────────────
POST /api/chat
Content-Type: application/json
Request Body:
{
"sessionId": "uuid", // Required
"userId": "user-xyz", // Required
"userName": "Rahul", // Required
"message": "Can you explain again?", // Required
"isTeacher": false // Optional (default: false)
}
Notes:
- Broadcasts chat_message SSE to ALL participants
Response:
{
"success": true,
"data": {
"id": "msg-uuid",
"userId": "user-xyz",
"userName": "Rahul",
"message": "Can you explain again?",
"isTeacher": false,
"timestamp": "2026-04-14T16:10:00.000Z"
}
}
── 6.2 Get Chat History ─────────────────────────────────────────────────────
GET /api/chat?sessionId=UUID&limit=100
Query Parameters:
sessionId string Required Session ID
limit number Optional Max messages to return (default: 100, max stored: 500)
Response:
{
"success": true,
"data": {
"messages": [ { "id": "...", "message": "...", ... } ],
"count": 23
}
}
═══════════════════════════════════════════════════════════════════════════════
ERROR RESPONSES
═══════════════════════════════════════════════════════════════════════════════
All error responses follow this format:
{
"success": false,
"error": "Descriptive error message"
}
HTTP Status Codes:
400 Bad Request - Missing or invalid fields
404 Not Found - Session/Poll/Doubt not found
500 Internal Error - Server error
═══════════════════════════════════════════════════════════════════════════════
COMPLETE INTEGRATION EXAMPLE (Student Frontend - Vanilla JS)
═══════════════════════════════════════════════════════════════════════════════
const BASE = 'https://Mafia2008-sar.hf.space';
let sessionId = null;
let myUserId = 'student-' + Math.random().toString(36).substr(2, 9);
let myName = 'Rahul Kumar';
// STEP 1: Look up session by room code
async function joinByRoomCode(roomCode) {
// Lookup
const lookup = await fetch(`${BASE}/api/session`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'lookup', roomCode })
}).then(r => r.json());
if (!lookup.success) return alert('Room not found');
sessionId = lookup.data.id;
// Join
await fetch(`${BASE}/api/session`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'join',
sessionId,
userId: myUserId,
userName: myName
})
});
// STEP 2: Subscribe to SSE
connectSSE();
}
// STEP 2: SSE Connection
function connectSSE() {
const es = new EventSource(`${BASE}/api/events?sessionId=${sessionId}&userId=${myUserId}`);
es.addEventListener('slide_change', e => {
const { imageBase64 } = JSON.parse(e.data);
document.getElementById('slide').src = imageBase64;
});
es.addEventListener('new_poll', e => {
const poll = JSON.parse(e.data);
showPoll(poll);
});
es.addEventListener('correct_answer', e => {
const { correctOptionIds } = JSON.parse(e.data);
highlightAnswer(correctOptionIds);
});
es.addEventListener('doubt_solved', e => {
const doubt = JSON.parse(e.data);
showDoubtResolution(doubt);
});
es.addEventListener('session_ended', () => {
alert('Class ended');
es.close();
});
}
// STEP 3: Vote on poll
async function vote(pollId, option) {
await fetch(`${BASE}/api/poll`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'vote', sessionId, pollId,
option, studentId: myUserId
})
});
}
// STEP 4: Submit doubt
async function submitDoubt(text) {
await fetch(`${BASE}/api/doubt`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'create', sessionId,
studentId: myUserId, studentName: myName,
question: text
})
});
}
═══════════════════════════════════════════════════════════════════════════════
COMPLETE INTEGRATION EXAMPLE (Teacher - Electron/SlideMap App)
═══════════════════════════════════════════════════════════════════════════════
const BASE = 'https://Mafia2008-sar.hf.space';
const teacherId = 'teacher-001';
const teacherName = 'Mr. Sharma';
let sessionId = null;
// STEP 1: Create session
async function createSession(sessionName) {
const res = await fetch(`${BASE}/api/session`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'create', teacherId, teacherName, sessionName
})
}).then(r => r.json());
sessionId = res.data.id;
const roomCode = res.data.roomCode; // Show this to students
console.log('Room Code:', roomCode);
// Connect SSE to receive doubts + student events
connectSSE();
}
// STEP 2: Connect to SSE (teacher also connects to receive doubts)
function connectSSE() {
const es = new EventSource(`${BASE}/api/events?sessionId=${sessionId}&userId=${teacherId}`);
es.addEventListener('new_doubt', e => {
const doubt = JSON.parse(e.data);
showDoubtInPanel(doubt); // Teacher sees student doubt
});
es.addEventListener('student_joined', e => {
const { userName, studentCount } = JSON.parse(e.data);
console.log(`${userName} joined. Total: ${studentCount}`);
});
}
// STEP 3: Send slide when screen changes
async function sendSlide(base64Image, slideIndex) {
await fetch(`${BASE}/api/slide`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'upload', sessionId,
slideImage: base64Image,
slideNumber: slideIndex
})
});
}
// STEP 4: Create poll
async function createPoll(question, options, correctAnswer) {
const res = await fetch(`${BASE}/api/poll`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'create', sessionId, question, options,
correctAnswer, duration: 0
})
}).then(r => r.json());
return res.data.id; // pollId
}
// STEP 5: End poll and reveal answer
async function endPoll(pollId, correctAnswer) {
await fetch(`${BASE}/api/poll`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'end', sessionId, pollId })
});
await fetch(`${BASE}/api/poll`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'setCorrectAnswers', sessionId, pollId, correctAnswer
})
});
}
// STEP 6: Resolve a doubt
async function resolveDoubt(doubtId, response) {
await fetch(`${BASE}/api/doubt`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'solve', sessionId, doubtId,
teacherResponse: response
})
});
}
// STEP 7: End session
async function endSession() {
await fetch(`${BASE}/api/session`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'end', sessionId })
});
}
═══════════════════════════════════════════════════════════════════════════════
QUICK REFERENCE CHEATSHEET
═══════════════════════════════════════════════════════════════════════════════
Action Method Endpoint action field
─────────────────────────────────────────────────────────────────────────
Create session (T) POST /api/session "create"
Lookup by room code (S) POST /api/session "lookup"
Join session (S) POST /api/session "join"
End session (T) POST /api/session "end"
List all sessions POST /api/session "list"
─────────────────────────────────────────────────────────────────────────
Subscribe to live events GET /api/events (SSE stream)
─────────────────────────────────────────────────────────────────────────
Upload + broadcast slide POST /api/slide "upload"
Go to slide number (T) POST /api/slide "goto"
Next slide (T) POST /api/slide "next"
Prev slide (T) POST /api/slide "prev"
─────────────────────────────────────────────────────────────────────────
Create poll (T) POST /api/poll "create"
Vote on poll (S) POST /api/poll "vote"
End poll (T) POST /api/poll "end"
Reveal answer (T) POST /api/poll "setCorrectAnswers"
Get all polls GET /api/poll ?sessionId=UUID
─────────────────────────────────────────────────────────────────────────
Submit doubt (S) POST /api/doubt "create"
Resolve doubt (T) POST /api/doubt "solve"
Update doubt status (T) POST /api/doubt "update"
Get all doubts (T) GET /api/doubt ?sessionId=UUID
─────────────────────────────────────────────────────────────────────────
Send chat message POST /api/chat (no action field)
Get chat history GET /api/chat ?sessionId=UUID&limit=N
─────────────────────────────────────────────────────────────────────────
(T) = Teacher action (S) = Student action
═══════════════════════════════════════════════════════════════════════════════
END OF DOCUMENTATION
Interactive UI: https://Mafia2008-sar.hf.space/api-docs
═══════════════════════════════════════════════════════════════════════════════