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):

typedescription
answer_changeUser changed answer
question_enterNavigated to question
tab_blurWindow lost focus
idle_detectedNo interaction for 30s+
tool_openCalculator/notepad opened
session_completeFinal 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:

ParameterTypeDefaultDescription
exam_idstringrequiredTarget exam ID
question_countint20Number of questions (5-100)
modestringweaknessweakness, 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:

ParameterTypeDefaultDescription
limitint50Max exams to process (1-600)
concurrencyint5Parallel 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:

ParameterTypeRequiredDescription
question_idstringYesQuestion to explain
user_questionstringNoFollow-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:

ToolPurposeArgs
draw_diagramSVG illustrationsvg, caption
highlight_answerHighlight optionoption_id, color, annotation
show_formulaLaTeX equationlatex, explanation
show_stepsNumbered stepssteps[], 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:

TypeDescription
answer_changeUser changed their answer
question_enterUser navigated to question
tab_blurWindow lost focus
idle_detectedNo interaction for 30s+
tool_openCalculator/notepad opened
question_flaggedUser flagged question
session_completeFinal session summary

Error Responses

All errors follow this format:

{
  "detail": "Error message describing what went wrong"
}

Status Codes:

CodeMeaning
400Bad Request - Invalid input
403Forbidden - Invalid API key
404Not Found - Resource doesn't exist
500Internal 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