Document: docs/API_SPEC.md
Status: Approved | Version: 1.0.0
Generates a signed JSON Web Token (JWT) granting access to join a specified LiveKit meeting room with participant permissions.
- Route:
GET /api/livekit-token - Location:
app/api/livekit-token/route.ts - Auth: Public client endpoint; authenticated via server-side
LIVEKIT_API_KEYandLIVEKIT_API_SECRET.
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
room |
string |
Yes | Unique Sabha room identifier | sabha-room-402 |
username |
string |
Yes | Participant identity / peer ID | peer_9x8f2a or User Name |
isHost |
string |
No | Boolean string (true/false) granting room admin grants |
true |
200 OK
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"wsUrl": "wss://sabha-meet.livekit.cloud"
}400 Bad Request
{
"error": "Missing room or username"
}500 Internal Server Error
{
"error": "LiveKit credentials are not configured in environment variables"
}Logs participant login telemetry, public IP address, user agent, and timestamp to Firestore for compliance and forensic auditing.
- Route:
POST /api/auth/record-login - Location:
app/api/auth/record-login/route.ts - Auth: Client authenticated via Google OAuth UID
{
"uid": "google_user_uid_12345",
"email": "user@example.com",
"displayName": "Verified User",
"photoURL": "https://lh3.googleusercontent.com/..."
}Instantaneous participant teardown endpoint invoked during app close, browser termination, navigation, or participant kick actions via navigator.sendBeacon and fetch. Purges participant documents from Firestore and immediately terminates the LiveKit session.
- Route:
POST /api/room/leave - Location:
app/api/room/leave/route.ts - Transport: Standard JSON fetch or
navigator.sendBeacon(Blob)
{
"roomId": "sabha-140-802",
"participantId": "peer_abc123"
}200 OK
{
"success": true
}When falling back to P2P Mesh or synchronizing room state, Sabha uses Firestore document collections under the root path /rooms/{roomId}.
- Path:
/rooms/{roomId} - Interface:
RoomSettings(lib/types.ts)
{
roomId: string; // Unique room slug or uuid
hostId: string; // Authenticated Google UID of the room creator
hostName: string; // Display name of the host
title: string; // e.g. "Sabha Weekly Sync"
isLocked: boolean; // If true, new joiners are denied entry
requireVideo: boolean; // If true, host mandates all webcams remain active
allowScreenShare: boolean; // Host permission toggle for participants
allowChat: boolean; // Host permission toggle for participants
allowUnmute: boolean; // Host permission toggle for participants
createdAt: number; // Unix timestamp (ms)
endedAt?: number; // Unix timestamp if room closed
}- Path:
/rooms/{roomId}/participants/{peerId} - Interface:
Participant(lib/types.ts)
{
id: string; // Client peer ID
uid: string; // User UID (Firebase Auth or guest ID)
name: string; // Display name
photoURL?: string | null; // Google profile avatar or null
isHost: boolean; // Whether user is room host (सभापति)
audioEnabled: boolean; // Mic muted/unmuted state
videoEnabled: boolean; // Camera enabled/disabled state
screenSharing: boolean; // Screen share status
isHandRaised: boolean; // Hand raise queue status
isMutedByHost: boolean; // Whether muted by host command
joinedAt: number; // Unix timestamp (ms)
}- Path:
/rooms/{roomId}/messages/{messageId} - Interface:
ChatMessage(lib/types.ts)
{
id: string; // Document ID
senderId: string; // Peer ID of the sender
senderName: string; // Display name of sender
senderPhoto?: string | null;// Avatar image URL
text: string; // Message text content
timestamp: number; // Unix epoch timestamp (ms)
isSystem?: boolean; // System event notifications (e.g. joined/left)
to?: string; // 'everyone' or target peerId for private DM
}- Path:
/rooms/{roomId}/reactions/{reactionId} - Interface:
ReactionItem(lib/types.ts)
{
id: string; // Document ID
emoji: string; // Emoji character (e.g. "👍", "❤️", "🎉")
senderId: string; // Peer ID of sender
senderName: string; // Sender display name
timestamp: number; // Unix epoch timestamp
}- Path:
/rooms/{roomId}/signals/{signalId} - Interface:
SignalData(lib/types.ts)
Used for SDP offer/answer exchanges and host moderator signals when operating in Mesh mode:
{
from: string; // Source peer ID
to: string; // Target peer ID or 'broadcast'
type: 'offer' | 'answer' | 'candidate' | 'mute-command' | 'kick-command';
payload: any; // RTCSessionDescriptionInit, RTCIceCandidateInit, etc.
timestamp: number; // Unix epoch timestamp
}mute-command: Instructs the target participant's client to disable local audio track (stream.getAudioTracks()[0].enabled = false) and update presence state.kick-command: Instructs the target participant's client to unmount WebRTC listeners, tear down streams, and navigate back to the home route (/).
- Path:
/users/{uid} - Subcollection:
/users/{uid}/loginHistory/{loginId}
// /users/{uid} document
{
uid: string;
email: string;
displayName: string;
photoURL: string;
lastLoginAt: number;
lastLoginIp: string;
}
// /users/{uid}/loginHistory/{loginId} document
{
ip: string;
userAgent: string;
timestamp: number;
}When running multiple tabs on the same origin (such as during local development and testing):
- Channel Name:
sabha_room_${roomId} - Payload Format: Emits exact
SignalDataJSON frames directly over the browser's nativeBroadcastChannelAPI, bypassing Firestore round-trips for zero network overhead.
When operating in LiveKit SFU mode, real-time collaboration messages (such as Whiteboard strokes) are transmitted using reliable binary DataChannels (room.localParticipant.publishData):
{
"type": "whiteboard",
"event": {
"type": "draw",
"x": 240,
"y": 180,
"prevX": 235,
"prevY": 178,
"color": "#10b981",
"width": 3,
"isErasing": false
}
}{
"type": "whiteboard",
"event": {
"type": "clear"
}
}Generates an executive AI summary using Google Gemini (gemini-3.5-flash with fallback to gemini-3.8-flash and gemini-flash-latest) from speaker-tagged meeting transcripts and dispatches emails via Zoho Mail SMTP (smtp.zoho.in:465).
- Hosts & Co-hosts: Receive the formatted AI Executive Summary + Action Items with the complete verbatim transcript attached as
sabha-[roomId]-transcript.txt(including bilingual Hindi dialogue and English translations). - Participants/Attendees: Receive the clean AI Executive Summary & Action Items notes delivered individually to their email inbox.
- Roster Resilience: Automatically queries Firestore
/rooms/{roomId}/participantsto merge any attendee who registered their email upon entry, ensuring no attendee is missed even if disconnected right before call end.
{
"roomId": "string",
"title": "string (optional)",
"durationMinutes": 15,
"participants": [
{
"id": "string",
"name": "string",
"email": "user@example.com",
"isHost": true,
"isCoHost": false
}
],
"transcript": [
{
"senderName": "Harsh",
"text": "नमस्ते, सभा में आपका स्वागत है।",
"translation": "Hello, welcome to Sabha.",
"timestamp": 1727700000000
}
]
}{
"success": true,
"summary": "# 📋 Sabha Meeting Summary\n...",
"emailsDispatched": {
"hosts": 1,
"attendees": 4
},
"totalHostRecipients": 1,
"totalAttendeeRecipients": 4
}Translates spoken Hindi speech segments to English using Gemini (gemini-3-flash-preview / gemini-3.5-flash / gemini-3.8-flash) with fast public gateway fallback.
{
"text": "नमस्ते आप सब कैसे हैं?",
"from": "hi",
"to": "en"
}{
"translation": "Hello, how are you all?"
}Transcribes microphone audio segments for browsers where Google Web Speech API is blocked or disabled (such as Brave Browser due to privacy shields) using Google Gemini multimodal audio understanding.
{
"audio": "<base64_encoded_audio_data>",
"mimeType": "audio/webm",
"language": "hi-IN"
}{
"text": "नमस्ते, क्या आप मुझे सुन सकते हैं?",
"translation": "Hello, can you hear me?"
}