Api-reference
Complete reference for the LearnPanta REST API.
Base URL: https://your-domain.com/api/v1
Authentication: Firebase ID token via Authorization: Bearer <token>
Authentication
Most endpoints require a Firebase ID token passed in the header:
curl -H "Authorization: Bearer <id_token>" https://api.example.com/api/v1/exams
For guest flows, specific endpoints accept X-Guest-Token instead of auth.
Sessions
Manage exam sessions and workflows.
Create Session
POST /sessions
Creates a new exam session and starts the Temporal workflow.
Request Body:
{
"paper_id": "uuid-string"
}
Response 200 OK:
{
"id": "session-uuid",
"paper_id": "paper-uuid",
"candidate_id": "user-id",
"status": "IN_PROGRESS",
"started_at": "2024-01-15T10:30:00Z",
"responses": {},
"feedback": null
}
Get Session
GET /sessions/{session_id}
Retrieve session details including responses and feedback.
Response 200 OK:
{
"id": "session-uuid",
"paper_id": "paper-uuid",
"candidate_id": "user-id",
"status": "COMPLETED",
"started_at": "2024-01-15T10:30:00Z",
"finished_at": "2024-01-15T12:00:00Z",
"responses": {
"q1": {"selected": "B", "time_ms": 45000}
},
"feedback": {
"readiness_score": 78,
"summary": "Strong performance...",
"recommendations": ["Review topic X"]
}
}
List Sessions
GET /sessions?candidate_id={candidate_id}&skip=0&limit=100
List all sessions, optionally filtered by candidate.
Pause Session
POST /sessions/{session_id}/pause
Signals the workflow to pause. Stops the timer.
Response 200 OK:
{
"status": "paused",
"session_id": "session-uuid"
}
Resume Session
POST /sessions/{session_id}/resume
Signals the workflow to resume from paused state.
Finalize Session
POST /sessions/{session_id}/finalize
Ends the session and triggers AI feedback generation.
Response 200 OK:
{
"status": "finalized",
"session_id": "session-uuid"
}
Public Diagnostic (Guest)
Guest users can start a short diagnostic without authentication.
Start Diagnostic
POST /public/diagnostic
Request Body:
{
"exam_id": "uuid-string"
}
Response 200 OK:
{
"session_id": "session-uuid",
"guest_token": "guest-token-string"
}
Use X-Guest-Token: <guest_token> for subsequent session and telemetry calls.
Debrief Orchestrator
The engine for the "Double Teacher" review experience.
Orchestrate Review (Script Generation)
POST /debrief/orchestrate
Generates a timed JSON script (Script) composed of "beats" for the frontend to play. This is the "Conductor".
Request Body:
{
"sessionId": "session-uuid",
"questions": [
{
"id": "q1",
"index": 0,
"questionText": "Question text",
"options": [{ "id": "A", "text": "..." }],
"userAnswer": "A",
"correctAnswer": "B",
"isCorrect": false,
"explanation": "..."
}
],
"wrongQuestions": [/* subset of questions */],
"scorePercentage": 72,
"pace": "normal",
"depth": "brief"
}
Response 200 OK:
{
"totalDurationMs": 45000,
"beats": [
{
"id": "q1_explain",
"startMs": 0,
"speech": { "text": "Let's review..." },
"canvas": { "instruction": "Draw diagram...", "clear": true }
}
]
}
Generate Brief (Paragraph)
POST /debrief/brief
Generates a concise explanatory paragraph for a single question. Critical: This is the prompt source for the autonomous Canvas Agent.
Request Body:
{
"questionText": "What is...",
"options": [...],
"correctAnswer": "B",
"userAnswer": "A"
}
Response 200 OK:
{
"text": "The correct answer is B because... A is wrong because..."
}
Text-to-Speech (TTS)
POST /debrief/tts
Synthesizes speech using Google Cloud TTS.
Request Body:
{
"text": "Hello world",
"voice": "en-US-Studio-O"
}
Response 200 OK:
{ "audio_base64": "..." }
Stream Explanation (Legacy/SSE)
GET /debrief/stream/{session_id}?question_id={id}
Streams an explanation using Server-Sent Events (SSE). Use /orchestrate for the main review flow.
Telemetry & Workflow Control
WebSocket Telemetry
WS /ws/stream/{session_id}
Streams behavioral metrics to the workflow. Messages are JSON; server responds with {"status":"received"}.
Metric Types (non-exhaustive):
| type | description |
|---|---|
answer_change | User changed answer |
question_enter | Navigated to question |
tab_blur | Window lost focus |
idle_detected | No interaction for 30s+ |
tool_open | Calculator/notepad opened |
session_complete | Final session summary |
Update Visible Question
POST /session/{session_id}/context
Signals which question is currently on screen.
Request Body:
{ "question_id": "q123" }
Upload Scratchpad
POST /sessions/{session_id}/scratchpad
Accepts Base64 image payload from companion device; broadcasts to active WebSocket clients.
Request Body:
{
"image_data": "data:image/jpeg;base64,...",
"client_timestamp": 1712345678
}
Notes:
- Scratchpad images are attached to the session and can be analyzed during AI feedback/debrief (multimodal).
- Keep images reasonably sized for fast uploads and model processing (large images may be skipped).
Finalize Session (signal only)
POST /session/{session_id}/finalize
Signals Temporal workflow to exit and generate final report.
Academic
Exam catalog and adaptive learning.
List Exams
GET /exams?skip=0&limit=100
Response 200 OK:
[
{
"id": "pmp-certification",
"name": "PMP Certification",
"description": "Project Management Professional...",
"category": "Project Management",
"is_active": true
}
]
Get Exam Papers
GET /exams/{exam_id}/papers
List all papers for an exam.
Get Paper Content
GET /papers/{paper_id}
Fetch full paper with sections and questions.
Response 200 OK:
{
"id": "paper-uuid",
"exam_id": "pmp-certification",
"title": "Practice Exam 1",
"total_duration_mins": 180,
"sections": [
{
"id": "section-uuid",
"title": "Domain 1: People",
"section_questions": [
{
"sequence_number": 1,
"question": {
"id": "q1",
"question_text": "A project manager is...",
"options": [
{"id": "A", "text": "Option A"},
{"id": "B", "text": "Option B"}
],
"correct_answer": "B",
"explanation": "The answer is B because..."
}
}
]
}
]
}
Create Adaptive Paper
POST /papers/adaptive?exam_id={exam_id}&question_count=20&mode=weakness
Generate a personalized paper based on candidate's learning profile.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
exam_id | string | required | Target exam ID |
question_count | int | 20 | Number of questions (5-100) |
mode | string | weakness | weakness, balanced, or strength |
Response 200 OK:
{
"id": "paper-uuid",
"exam_id": "pmp-certification",
"title": "Adaptive Practice - Weakness Focus",
"questions_selected": 20,
"target_tags": ["risk-management", "stakeholder-engagement"]
}
Get Learning Profile
GET /learning-profile
Get authenticated candidate's performance profile with weakness analysis.
Response 200 OK:
{
"candidate_id": "user-id",
"profile": [
{"tag": "risk-management", "correct": 3, "total": 10, "accuracy": 0.3},
{"tag": "scheduling", "correct": 8, "total": 10, "accuracy": 0.8}
],
"total_tags_analyzed": 12,
"top_weakness": {"tag": "risk-management", "accuracy": 0.3}
}
Enroll in Exam
POST /enroll?exam_id={exam_id}
Enroll the authenticated candidate in an exam (candidate inferred from token).
Get Enrollments
GET /enrollments
List authenticated candidate's enrolled exams.
Curator
Content generation and management.
Curate Exam
POST /curator/curate/{exam_id}
Trigger AI content generation for a specific exam.
Batch Curate
POST /curator/batch-curate?limit=50&concurrency=5
Batch curate multiple exams with concurrency control.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | int | 50 | Max exams to process (1-600) |
concurrency | int | 5 | Parallel workers (1-10) |
Curation Status
GET /curator/status
Get content curation progress.
Response 200 OK:
{
"total_exams": 584,
"exams_with_content": 73,
"exams_pending": 511,
"completion_percentage": 12.5,
"total_questions": 3860,
"ai_generated_questions": 940
}
Questions
Direct question management.
Create Question
POST /questions
List Questions
GET /questions?skip=0&limit=100&domain={domain}
Get Question
GET /questions/{question_id}
Delete Question
DELETE /questions/{question_id}
Debrief
AI-powered streaming explanations for post-exam review.
Text-to-Speech (TTS)
POST /debrief/tts
Request Body:
{
"text": "Short explanation to speak",
"voice": "en-US-Journey-D",
"speed": 0.95
}
Response 200 OK:
{
"audio_base64": "base64-encoded-mp3"
}
Stream Debrief (SSE)
GET /debrief/stream/{session_id}?question_id={question_id}&user_question={optional}
Streams AI explanation with chain-of-thought reasoning and tool invocations.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
question_id | string | Yes | Question to explain |
user_question | string | No | Follow-up question from user |
SSE Event Types:
event: thinking
data: {"text": "Analyzing the question..."}
event: speech
data: {"text": "The correct answer is B because..."}
event: tool_call
data: {"name": "draw_diagram", "args": {"svg": "...", "caption": "..."}}
event: done
data: {"status": "complete"}
Available Tools in Stream:
| Tool | Purpose | Args |
|---|---|---|
draw_diagram | SVG illustration | svg, caption |
highlight_answer | Highlight option | option_id, color, annotation |
show_formula | LaTeX equation | latex, explanation |
show_steps | Numbered steps | steps[], title |
Explain Question (Non-Streaming)
POST /debrief/explain/{session_id}
Request Body:
{
"question_id": "q1",
"user_question": "Why is B wrong?"
}
Response 200 OK:
{
"question_id": "q1",
"explanation": "The answer is...",
"is_correct": false,
"user_answer": "B",
"correct_answer": "C"
}
Search
Semantic search for questions using embeddings.
Semantic Search (POST)
POST /search/questions
Request Body:
{
"query": "database normalization rules",
"top_k": 10,
"exam_id": "optional-filter",
"difficulty": "medium",
"min_score": 0.5
}
Response 200 OK:
{
"query": "database normalization rules",
"count": 5,
"results": [
{
"id": "q123",
"question_text": "Which normal form...",
"score": 0.89,
"exam_id": "db-fundamentals",
"difficulty": "medium"
}
]
}
Semantic Search (GET)
GET /search/questions?q={query}&top_k=10&exam_id={optional}&min_score=0.5
Same response format as POST version.
Find Similar Questions
GET /search/similar/{question_id}?top_k=5&exclude_same_exam=false
Finds questions semantically similar to an existing question.
Response 200 OK:
{
"source_question_id": "q123",
"count": 5,
"similar_questions": [
{"id": "q456", "score": 0.92, "question_text": "..."}
]
}
Embed Questions
POST /search/embed
Manually trigger embedding for specific questions (backfilling).
Request Body: ["q1", "q2", "q3"]
Embed Exam Questions
POST /search/embed-exam/{exam_id}
Embed all questions for an exam into Pinecone.
Get Index Stats
GET /search/stats
Returns Pinecone index statistics (vector count, namespaces).
Analytics
Metrics and time-series data from TimescaleDB.
Session Analytics
GET /analytics/session/{session_id}?metric_type={optional}&limit=500
Returns detailed time-series metrics for a session.
Response 200 OK:
{
"session_id": "sess-123",
"metric_count": 150,
"metrics": [
{
"timestamp": "2024-01-15T10:31:00Z",
"metric_type": "focus_score",
"value": {"score": 85},
"question_id": "q1"
}
]
}
Focus Trend
GET /analytics/session/{session_id}/focus-trend?bucket_seconds=60
Returns focus score bucketed by time intervals.
Response 200 OK:
{
"session_id": "sess-123",
"bucket_seconds": 60,
"data_points": 45,
"trend": [
{"bucket": "2024-01-15T10:30:00Z", "avg_focus": 92},
{"bucket": "2024-01-15T10:31:00Z", "avg_focus": 88}
]
}
Candidate History
GET /analytics/candidate/{candidate_id}/history?days=30&limit=50
Returns aggregated session summaries for progress tracking.
Exam Analytics
GET /analytics/exam/{exam_id}?days=30
Returns aggregate stats: average scores, completion rates, performance distribution.
Platform Stats (Admin)
GET /analytics/platform/stats?days=7
Platform-wide health and usage metrics.
Record Metric (testing/internal)
POST /analytics/session/{session_id}/record
Stores a single metric event (used by tests/tools; production metrics come via workflow).
Body:
{
"metric_type": "focus_score",
"value": {"score": 92},
"question_id": "q1",
"candidate_id": "user-123",
"exam_id": "pmp"
}
WebSocket
Real-time telemetry streaming.
Telemetry Stream
WS /ws/stream/{session_id}
Connect to stream behavioral metrics during an exam session.
Message Format (Client → Server):
{
"type": "answer_change",
"question_id": "q1",
"from": "A",
"to": "B",
"timestamp": 1705312200000
}
Metric Types:
| Type | Description |
|---|---|
answer_change | User changed their answer |
question_enter | User navigated to question |
tab_blur | Window lost focus |
idle_detected | No interaction for 30s+ |
tool_open | Calculator/notepad opened |
question_flagged | User flagged question |
session_complete | Final session summary |
Error Responses
All errors follow this format:
{
"detail": "Error message describing what went wrong"
}
Status Codes:
| Code | Meaning |
|---|---|
| 400 | Bad Request - Invalid input |
| 403 | Forbidden - Invalid API key |
| 404 | Not Found - Resource doesn't exist |
| 500 | Internal Server Error |
Rate Limits: Not enforced yet. Recommended before public release: 100 req/min per IP, 20 req/min per key on write endpoints; separate limits for streaming/SSE and WebSocket upgrades.
Next Steps
- Architecture - System overview
- Security - Authentication details
- Configuration - Environment variables