REST API reference for the Claw-ED web server (clawed serve).
http://localhost:8000
The default port is 8000. Override with --port:
clawed serve --port 9000
All endpoints (except /api/health and public share links) require authentication.
Include the token in the Authorization header:
Authorization: Bearer YOUR_TOKEN
The token is auto-generated on first server start and stored at ~/.eduagent/api_token. Find it with:
cat ~/.eduagent/api_token
For browser sessions, use the POST bootstrap endpoint:
POST /api/auth/bootstrap
Content-Type: application/x-www-form-urlencoded
token=YOUR_TOKEN
This sets an HttpOnly, SameSite=Strict session cookie (clawed_token) and redirects to the dashboard. The cookie persists for 24 hours.
If you visit a protected page without a cookie, you’ll see a login form that POSTs to this endpoint automatically.
Note: The old
?token=URL parameter flow has been removed to prevent token leakage through browser history, bookmarks, and referrer headers.
The Chrome extension stores the API token in chrome.storage.sync and sends it as a Bearer header on every request. Configure the token in the extension popup.
Student-facing classroom routes (/api/classroom/{code}/state, /api/classroom/{code}/respond, WebSocket) do not require a Bearer token. The class code itself serves as the session auth — students get the code from their teacher in class.
Teacher classroom control routes (start session, advance slides, launch polls) require the Bearer token.
Set EDUAGENT_LOCAL_AUTH_BYPASS=1 to skip authentication for requests from 127.0.0.1 / ::1. Useful for local development and testing. Do not enable this on shared or network-accessible deployments.
Rate limits are enforced per-client-IP, in-memory. Limits reset when the server restarts.
| Endpoint Category | Limit |
|---|---|
Generation endpoints (/api/unit, /api/lesson, /api/materials, /api/full, /api/course) |
10/minute |
Chat endpoints (/api/chat, /api/gateway/chat) |
30/minute |
Read-only endpoints (/api/health, /api/settings, /api/units, /api/templates, /api/score/*) |
60/minute |
Tool endpoints (/api/sub-packet, /api/parent-comm) |
10/minute |
Feedback & improvement (/api/feedback, /api/improve) |
10/minute |
Exceeding the limit returns 429 Too Many Requests:
{
"detail": "Rate limit exceeded (10/minute)"
}
GET /api/healthLightweight liveness check. No authentication required. No database or LLM calls.
Response:
{
"status": "ok",
"version": "4.9.2026.14"
}
GET /api/health/diagnosticsFull diagnostics including database stats and LLM connection test. Requires authentication.
Response:
{
"status": "ok",
"llm_provider": "anthropic",
"llm_model": "claude-sonnet-4-6",
"llm_connected": true,
"persona_loaded": true,
"units_generated": 12,
"lessons_generated": 48,
"db_size_mb": 3.41,
"version": "4.9.2026.14"
}
GET /api/settingsGet current settings. API keys are masked in the response.
Response:
{
"provider": "anthropic",
"anthropic_model": "claude-sonnet-4-6",
"openai_model": "gpt-4o",
"ollama_model": "llama3.2",
"ollama_base_url": "http://localhost:11434",
"anthropic_key_masked": "sk-ant-...xxxx",
"openai_key_masked": null,
"has_anthropic_key": true,
"has_openai_key": false,
"include_homework": true,
"export_format": "markdown"
}
POST /api/settingsSave settings and optionally update an API key.
Request:
{
"provider": "anthropic",
"api_key": "sk-ant-api03-...",
"anthropic_model": "claude-sonnet-4-6",
"openai_model": "gpt-4o",
"ollama_model": "llama3.2",
"ollama_base_url": "http://localhost:11434",
"default_grade": "8",
"default_subject": "Science",
"include_homework": true,
"export_format": "markdown"
}
Response:
{
"status": "saved"
}
GET /api/settings/test-connectionTest the current LLM connection with a small probe request.
Response:
{
"connected": true,
"provider": "anthropic",
"model": "claude-sonnet-4-6",
"latency_ms": 342
}
POST /api/settings/clear-contentDelete all generated units and lessons. Persona and settings are preserved. Use with caution.
Response:
{
"status": "cleared"
}
POST /api/settings/resetFull factory reset. Deletes all data including persona, lessons, and settings.
Response:
{
"status": "reset"
}
POST /api/ingestUpload teaching materials (lesson plans, handouts, slideshows) to build the teacher persona and curriculum knowledge base.
Request: multipart/form-data with one or more files.
Supported formats: PDF, DOCX, DOC, PPTX, PPT, TXT, MD, HTML, XLSX.
curl -X POST http://localhost:8000/api/ingest \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "files=@my_lesson_plan.docx" \
-F "files=@unit_slides.pptx"
Response:
{
"teacher_id": "t_abc123",
"persona": {
"name": "Ms. Rodriguez",
"teaching_style": "socratic",
"subject_area": "History",
"grade_levels": ["7", "8"]
},
"documents_ingested": 2
}
GET /api/personaGet the current teacher persona.
Response:
{
"teacher_id": "t_abc123",
"persona": {
"name": "Ms. Rodriguez",
"teaching_style": "socratic",
"subject_area": "History",
"grade_levels": ["7", "8"]
}
}
Returns 404 if no persona exists (no files uploaded yet).
POST /api/unitGenerate a unit plan.
Request:
{
"topic": "The American Revolution",
"grade_level": "8",
"subject": "History",
"duration_weeks": 3,
"standards": ["8.1.a", "8.1.b"]
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
topic |
string | Yes | – | Lesson topic (1-500 chars) |
grade_level |
string | No | "8" |
Grade level |
subject |
string | No | "Science" |
Subject area |
duration_weeks |
integer | No | 3 |
Unit duration (1-52 weeks) |
standards |
string[] | No | [] |
Specific standards to align to |
Response:
{
"unit_id": "u_def456",
"unit": {
"title": "The American Revolution",
"subject": "History",
"grade_level": "8",
"topic": "The American Revolution",
"duration_weeks": 3,
"daily_lessons": [
{ "lesson_number": 1, "topic": "Causes of the Revolution" },
{ "lesson_number": 2, "topic": "Key Battles and Turning Points" }
]
}
}
POST /api/lessonGenerate a single lesson plan for an existing unit.
Request:
{
"unit_id": "u_def456",
"lesson_number": 1
}
Response:
{
"lesson_id": "l_ghi789",
"lesson": {
"lesson_number": 1,
"title": "Causes of the American Revolution",
"objective": "Students will analyze the economic and political causes...",
"standards": ["8.1.a"]
}
}
POST /api/materialsGenerate student materials (worksheets, handouts) for an existing lesson.
Request:
{
"lesson_id": "l_ghi789"
}
Response:
{
"lesson_id": "l_ghi789",
"materials": {
"worksheet_items": [...],
"vocabulary": [...],
"exit_ticket": [...]
}
}
POST /api/fullEnd-to-end pipeline: generates a unit plan, all lessons, and all materials. Returns a Server-Sent Events (SSE) stream with progress updates.
Request:
{
"topic": "Photosynthesis",
"grade_level": "7",
"subject": "Science",
"duration_weeks": 2,
"standards": [],
"include_homework": true,
"max_lessons": 5,
"template_slug": null
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
topic |
string | Yes | – | Topic (1-500 chars) |
grade_level |
string | No | "8" |
Grade level |
subject |
string | No | "Science" |
Subject area |
duration_weeks |
integer | No | 3 |
Unit duration |
standards |
string[] | No | [] |
Standards to align to |
include_homework |
boolean | No | true |
Generate homework assignments |
max_lessons |
integer | No | all | Cap the number of lessons generated |
template_slug |
string | No | null |
Lesson structure template to use |
Response: SSE stream with events:
event: progress
data: {"step": "unit", "status": "generating", "message": "Generating unit plan..."}
event: progress
data: {"step": "unit", "status": "done", "unit_id": "u_def456", "title": "Photosynthesis", "lesson_count": 5}
event: progress
data: {"step": "lesson", "status": "generating", "lesson_number": 1, "topic": "Introduction to Photosynthesis"}
event: progress
data: {"step": "lesson", "status": "done", "lesson_id": "l_ghi789", "lesson_number": 1, "title": "Introduction to Photosynthesis"}
event: done
data: {"unit_id": "u_def456", "lesson_count": 5}
GET /api/stream/unitStream unit plan generation via SSE (for EventSource clients).
Query parameters: topic (required), grade_level, subject, duration_weeks.
GET /api/stream/unit?topic=Fractions&grade_level=5&subject=Math
GET /api/stream/lessonStream a single lesson generation via SSE.
Query parameters: unit_id (required), lesson_number (default 1).
POST /api/courseGenerate a full course structure from a list of topics. Returns SSE stream.
Request:
{
"subject": "Science",
"grade_level": "7",
"topics": ["Cells", "Photosynthesis", "Ecosystems"],
"weeks_per_topic": 2
}
GET /api/score/{lesson_id}Score a lesson on quality dimensions (alignment, engagement, rigor).
Response:
{
"lesson_id": "l_ghi789",
"scores": {
"alignment": 4.2,
"engagement": 3.8,
"rigor": 4.0,
"overall": 4.0
}
}
POST /api/suggest/{lesson_id}Generate improvement suggestions for a lesson based on quality scores and teacher feedback.
Response:
{
"lesson_id": "l_ghi789",
"suggestions": [
"Add a higher-order thinking question to the exit ticket",
"Include a primary source excerpt in the direct instruction"
]
}
GET /api/templatesList all available lesson structure templates.
Response:
{
"templates": [
{
"name": "Jigsaw",
"slug": "jigsaw",
"description": "Cooperative learning through expert groups",
"best_for": "Complex topics with multiple subtopics"
},
{
"name": "Socratic Seminar",
"slug": "socratic_seminar",
"description": "Discussion-based inquiry",
"best_for": "Texts, primary sources, ethical dilemmas"
}
]
}
GET /api/unitsList all generated units.
Response:
{
"units": [
{
"id": "u_def456",
"title": "The American Revolution",
"subject": "History",
"grade_level": "8",
"lesson_count": 5
}
]
}
GET /api/lessons/{unit_id}List all lessons for a unit.
Response:
{
"lessons": [
{
"id": "l_ghi789",
"lesson_number": 1,
"title": "Causes of the American Revolution"
}
]
}
POST /api/feedbackSubmit a rating and notes for a generated lesson. Lessons rated 4+ are auto-contributed to the teaching corpus.
Request:
{
"lesson_id": "l_ghi789",
"rating": 4,
"notes": "Great primary source, but the exit ticket was too easy",
"sections_edited": ["exit_ticket"]
}
| Field | Type | Required | Description |
|---|---|---|---|
lesson_id |
string | Yes | The lesson to rate |
rating |
integer | Yes | 1-5 stars |
notes |
string | No | Free-text feedback |
sections_edited |
string[] | No | Which sections the teacher manually edited |
Response:
{
"feedback_id": "f_jkl012",
"message": "Feedback recorded. Thank you! This lesson has been added to your teaching corpus to improve future generations.",
"corpus_contributed": true
}
GET /api/feedback/{lesson_id}Get all feedback entries for a lesson.
Response:
{
"feedback": [
{
"id": "f_jkl012",
"rating": 4,
"notes": "Great primary source, but the exit ticket was too easy",
"created_at": "2026-04-07T14:30:00"
}
]
}
GET /api/feedback-analysisAggregate feedback analysis for a time window.
Query parameters: days (default 7).
GET /api/statsGet comprehensive analytics for the default teacher.
Query parameters: teacher_id (default "local-teacher").
GET /api/stats/{teacher_id}Get analytics for a specific teacher.
POST /api/improveTrigger a prompt improvement cycle based on recent feedback.
Request (optional):
{
"feedback_window_days": 7
}
GET /api/export/{lesson_id}Export a lesson as Markdown, PDF, or DOCX.
Query parameters: fmt – one of markdown (default), pdf, docx.
GET /api/export/l_ghi789?fmt=docx
Returns the file as a download.
POST /api/export/{lesson_id}/classroomGenerate a Google Classroom-compatible CourseWork JSON payload.
Response:
{
"lesson_id": "l_ghi789",
"coursework": {
"title": "Causes of the American Revolution",
"description": "Students will analyze...",
"materials": [...],
"maxPoints": 100,
"workType": "ASSIGNMENT",
"state": "DRAFT"
}
}
POST /api/lessons/{lesson_id}/shareGenerate a shareable URL for a lesson.
Response:
{
"share_url": "/shared/abc123token",
"token": "abc123token"
}
GET /api/share/{token}Retrieve a shared lesson by its share token. No authentication required.
Response:
{
"lesson_id": "l_ghi789",
"title": "Causes of the American Revolution",
"share_token": "abc123token",
"lesson": { ... }
}
POST /api/importImport a lesson from a share URL or token.
Request:
{
"url": "http://localhost:8000/shared/abc123token",
"token": null,
"server": "http://localhost:8000"
}
Provide either url or token. Import is restricted to localhost by default. Override with EDUAGENT_IMPORT_ALLOW_URLS.
POST /api/chatStudent chatbot – a student asks a question about a specific lesson and gets an answer in the teacher’s voice.
Request:
{
"lesson_id": "l_ghi789",
"question": "Why did the colonists dump the tea?",
"history": []
}
| Field | Type | Required | Description |
|---|---|---|---|
lesson_id |
string | Yes | Lesson to ground the response in |
question |
string | Yes | Student’s question |
history |
object[] | No | Prior chat messages (role/content pairs) |
Response:
{
"response": "Great question! The colonists dumped the tea as a protest against...",
"lesson_id": "l_ghi789"
}
POST /api/gateway/chatTeacher chat through the Gateway. The teacher_id is resolved server-side from config – callers cannot specify or impersonate a different teacher.
Request:
{
"message": "Make a lesson on the water cycle for 5th grade science"
}
Response:
{
"text": "Working on your lesson materials for \"The Water Cycle\" now...",
"files": ["/Users/you/clawed_output/Water_Cycle_teacher.docx"],
"buttons": []
}
POST /api/sub-packetGenerate a complete substitute teacher packet.
Request:
{
"teacher_name": "Ms. Rodriguez",
"school": "Lincoln Middle School",
"class_name": "Period 3 US History",
"grade": "8",
"subject": "History",
"date": "2026-04-10",
"period_or_time": "10:00 AM - 10:50 AM",
"lesson_topic": "The Boston Tea Party",
"lesson_context": "Students finished reading the Declaration of Independence yesterday"
}
Response:
{
"packet": { ... },
"markdown": "# Substitute Teacher Packet\n..."
}
POST /api/parent-commGenerate a professional parent communication email.
Request:
{
"comm_type": "progress_update",
"student_description": "7th grader who has been improving in class participation",
"class_context": "US History, currently studying the Civil War",
"tone": "professional and warm",
"additional_notes": "Mention the upcoming project"
}
Valid comm_type values: progress_update, behavior_concern, positive_note, upcoming_unit, permission_request, general_update.
POST /api/school/setupSet up a school for multi-teacher deployment.
Request:
{
"name": "Lincoln Middle School",
"state": "CA",
"district": "LAUSD",
"grade_levels": ["6", "7", "8"]
}
GET /api/school/{school_id}Get school details.
POST /api/school/add-teacherAdd a teacher to a school.
Request:
{
"school_id": "s_abc123",
"teacher_id": "t_def456",
"role": "teacher",
"department": "History"
}
GET /api/school/{school_id}/teachersList all teachers in a school.
POST /api/school/shareShare a unit or lesson to the school’s curriculum library.
Request:
{
"school_id": "s_abc123",
"teacher_id": "t_def456",
"content_type": "unit",
"content_id": "u_ghi789",
"department": "History"
}
GET /api/school/{school_id}/shared-libraryBrowse the school’s shared curriculum library. Returns top-rated content.
Query parameters: department (optional), limit (default 50).
POST /api/school/rate-sharedRate a piece of shared content (1-5).
Request:
{
"shared_id": "sh_xyz",
"rating": 5
}
POST /api/onboarding/persona-formCreate a teacher persona from the quick setup form (no file upload needed).
Request:
{
"name": "Ms. Rodriguez",
"subject_area": "History",
"grade_levels": "7, 8",
"teaching_style": "socratic",
"preferred_lesson_format": "I Do / We Do / You Do"
}
Valid teaching_style values: direct_instruction, socratic, inquiry_based, project_based.
POST /api/onboarding/stepUpdate onboarding progress for a teacher.
Request:
{
"teacher_id": "t_abc123",
"step": 2
}
GET /api/onboarding/stateGet the current onboarding state (persona status, step completed).
Response:
{
"has_persona": true,
"step_completed": 3,
"teacher_id": "t_abc123",
"persona": { ... }
}
All errors follow a consistent format:
{
"error": "Description of what went wrong"
}
| Status Code | Meaning |
|---|---|
400 |
Bad request – missing or invalid parameters |
401 |
Authentication required or invalid token |
404 |
Resource not found |
429 |
Rate limit exceeded |
500 |
Internal server error |
By default, CORS is restricted to http://localhost:8000 and http://127.0.0.1:8000. Chrome extension origins matching chrome-extension://<32-char-id> are allowed via regex.
Override with the EDUAGENT_CORS_ORIGINS environment variable (comma-separated list of origins).
Allowed methods: GET, POST. Allowed headers: Authorization, Content-Type.
| Variable | Description | Default |
|---|---|---|
EDUAGENT_CORS_ORIGINS |
Comma-separated allowed CORS origins | http://localhost:8000 |
EDUAGENT_LOCAL_AUTH_BYPASS |
Set to 1 to skip auth for localhost requests |
unset |
EDUAGENT_DATA_DIR |
Data directory for DB, token, and secrets | ~/.eduagent |
EDUAGENT_IMPORT_ALLOW_URLS |
Comma-separated additional allowed import URLs | unset |
CLAWED_AUTO_APPROVE |
Set to 1 to auto-approve write_local and network_call tools |
unset |