{"openapi":"3.1.0","info":{"title":"EPPP Tutor API","version":"1.0.0","description":"REST API for the EPPP study tutor (voice or otherwise). Every success response is `{ \"data\": ... }`; every error is `{ \"error\": { \"code\", \"message\", \"details?\" } }`. Authenticate with `Authorization: Bearer <api key>` (or `X-API-Key`). Keys are scoped; non-bound keys act for a learner via `X-Learner-Id`. All scores are practice percentages and never convert to the ASPPB 200-800 scaled score. The keyed answer is never returned before an answer is committed. Scopes: learners:read (Look up learners (by email or username) and read learner profiles); teach (Run study sessions: next question, retries, explanations, answers, flags, summaries); progress:read (Read mastery, metrics, weak areas, tips, review queue); exams:read (List/read exams); exams:write (Create and edit exams, add questions (including custom spots)); exams:take (Start exam attempts, submit answers, read results); homework:read (List/read homework and submissions); homework:write (Create homework assignments); homework:submit (Submit homework answers on behalf of a learner); homework:grade (Submit rubric-based grades and feedback for free-text answers); admin (Everything, plus API key management)"},"servers":[{"url":"https://www.amashiripiti.com"}],"tags":[{"name":"Meta"},{"name":"Progress"},{"name":"Catalog"},{"name":"Admin"},{"name":"Learners"},{"name":"Sessions"},{"name":"Teach"},{"name":"Exams"},{"name":"Homework"}],"paths":{"/api/openapi.json":{"get":{"operationId":"getOpenApi","tags":["Meta"],"summary":"This OpenAPI 3.1 document","description":"Required scope: any valid key.\n\nReturns: OpenAPI document (not wrapped in data)","security":[],"parameters":[],"responses":{"200":{"description":"OpenAPI document (not wrapped in data)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"OpenAPI document (not wrapped in data)"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/coach/instructions":{"get":{"operationId":"getCoachInstructions","tags":["Meta"],"summary":"Public coach instructions for a voice assistant or AI tutor (no API key, no learner data)","description":"Plain text by default; add ?format=json for { instructions }.\n\nRequired scope: any valid key.\n\nReturns: text/plain, or { instructions } with format=json (not wrapped in data)","security":[],"parameters":[{"name":"format","in":"query","required":false,"schema":{"type":"string","enum":["json","text"]}}],"responses":{"200":{"description":"text/plain, or { instructions } with format=json (not wrapped in data)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"text/plain, or { instructions } with format=json (not wrapped in data)"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/health":{"get":{"operationId":"getHealth","tags":["Meta"],"summary":"Liveness and database check","description":"Required scope: any valid key.\n\nReturns: { status: \"ok\" }","security":[],"parameters":[],"responses":{"200":{"description":"{ status: \"ok\" }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ status: \"ok\" }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/me/dashboard":{"get":{"operationId":"getDashboard","tags":["Progress"],"summary":"Everything the student dashboard shows, in one call","description":"Overview, domains vs EPPP weights, knowledge statements, trend, strengths and struggles, tips, review queue, recent sessions, homework and exams. The /dashboard page calls this with the learner's Supabase access token.\n\nRequired scope: progress:read.\n\nReturns: { overview, domains, knowledgeStatements, trend, weak, tips, queue, sessions, homework, exams }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ overview, domains, knowledgeStatements, trend, weak, tips, queue, sessions, homework, exams }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ overview, domains, knowledgeStatements, trend, weak, tips, queue, sessions, homework, exams }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/me":{"get":{"operationId":"whoAmI","tags":["Meta"],"summary":"Describe the calling API key and resolved learner","description":"Required scope: any valid key.\n\nReturns: { key: {id,name,scopes,bound_learner_id}, learner: {...}|null }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"X-Learner-Id","in":"header","required":false,"description":"Optional learner context (filters results).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ key: {id,name,scopes,bound_learner_id}, learner: {...}|null }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ key: {id,name,scopes,bound_learner_id}, learner: {...}|null }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/catalog/domains":{"get":{"operationId":"getDomains","tags":["Catalog"],"summary":"EPPP domains, weights and knowledge statements","description":"The ASPPB blueprint: eight domains with exam weights (10,13,11,12,16,15,7,16 percent) and their knowledge statements, plus how many spots the catalog holds for each.\n\nRequired scope: any valid key.\n\nReturns: { domains: [{id,name,eppp_weight_percent,spots_in_catalog,knowledge_statements:[{id,label,statement,spots_in_catalog}]}] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[],"responses":{"200":{"description":"{ domains: [{id,name,eppp_weight_percent,spots_in_catalog,knowledge_statements:[{id,label,statement,spots_in_catalog}]}] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ domains: [{id,name,eppp_weight_percent,spots_in_catalog,knowledge_statements:[{id,label,statement,spots_in_catalog}]}] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/catalog/spots":{"get":{"operationId":"listSpots","tags":["Catalog"],"summary":"List catalog spots (ids and structure only; no concepts, options or keys)","description":"Required scope: teach.\n\nReturns: { spots: [{spot_id,domain,knowledge_statement,variant_count,custom}] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"domain","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":8}},{"name":"kn","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ spots: [{spot_id,domain,knowledge_statement,variant_count,custom}] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ spots: [{spot_id,domain,knowledge_statement,variant_count,custom}] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/admin/keys":{"post":{"operationId":"createApiKey","tags":["Admin"],"summary":"Create an API key (the token is shown once)","description":"Required scope: admin.\n\nReturns: { token, key: {id,name,scopes,learner_id,...} }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"scopes":{"minItems":1,"type":"array","items":{"type":"string","enum":["learners:read","teach","progress:read","exams:read","exams:write","exams:take","homework:read","homework:write","homework:submit","homework:grade","admin"]}},"learner_id":{"description":"Bind the key to one learner; otherwise callers send X-Learner-Id","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"expires_in_days":{"type":"integer","minimum":1,"maximum":3650}},"required":["name","scopes"]}}}},"responses":{"201":{"description":"{ token, key: {id,name,scopes,learner_id,...} }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ token, key: {id,name,scopes,learner_id,...} }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listApiKeys","tags":["Admin"],"summary":"List API keys (never returns secrets)","description":"Required scope: admin.\n\nReturns: { keys: [...] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[],"responses":{"200":{"description":"{ keys: [...] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ keys: [...] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/admin/keys/{keyId}":{"delete":{"operationId":"revokeApiKey","tags":["Admin"],"summary":"Revoke an API key","description":"Required scope: admin.\n\nReturns: { revoked: true }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"keyId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ revoked: true }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ revoked: true }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/learners":{"get":{"operationId":"lookupLearner","tags":["Learners"],"summary":"Look up a learner (EPPP Prep account) by exact email or username","description":"Required scope: learners:read.\n\nReturns: Learner {id (auth user uuid),username,email,created_at}","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"email","in":"query","required":false,"schema":{"type":"string","format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"username","in":"query","required":false,"schema":{"type":"string","minLength":3,"maxLength":24}}],"responses":{"200":{"description":"Learner {id (auth user uuid),username,email,created_at}","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Learner {id (auth user uuid),username,email,created_at}"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/learners/{learnerId}":{"get":{"operationId":"getLearner","tags":["Learners"],"summary":"Get a learner","description":"Required scope: learners:read.\n\nReturns: Learner","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"learnerId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Learner","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Learner"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sessions":{"post":{"operationId":"startSession","tags":["Sessions"],"summary":"Start a study session (domains, duration, coaching or exam mode)","description":"voice_assistant.md 'Starting a session'. Mixed mode samples domains by EPPP weight.\n\nRequired scope: teach.\n\nReturns: Session {session_id,mode,status,domains,planned_minutes,minutes_remaining,...}","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"default":"coaching","type":"string","enum":["coaching","exam"]},"domains":{"description":"Domain ids 1-8; omit for mixed (EPPP-weighted)","maxItems":8,"type":"array","items":{"type":"integer","minimum":1,"maximum":8}},"planned_minutes":{"type":"integer","minimum":1,"maximum":480}}}}}},"responses":{"201":{"description":"Session {session_id,mode,status,domains,planned_minutes,minutes_remaining,...}","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Session {session_id,mode,status,domains,planned_minutes,minutes_remaining,...}"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listSessions","tags":["Sessions"],"summary":"List the learner's sessions, newest first","description":"Required scope: teach.\n\nReturns: { sessions: [...] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ sessions: [...] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ sessions: [...] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sessions/{sessionId}":{"get":{"operationId":"getSession","tags":["Sessions"],"summary":"Get a session (progress, time remaining)","description":"Required scope: teach.\n\nReturns: Session","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"Session","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Session"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sessions/{sessionId}/end":{"post":{"operationId":"endSession","tags":["Sessions"],"summary":"End the session and get the closing summary","description":"Idempotent. Ending reveals every concept touched (exam-mode review phase) and returns the 'Ending a session' summary.\n\nRequired scope: teach.\n\nReturns: Summary (see getSessionSummary)","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"Summary (see getSessionSummary)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Summary (see getSessionSummary)"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sessions/{sessionId}/next":{"post":{"operationId":"nextItem","tags":["Teach"],"summary":"Get the next question (shuffled; key kept server-side)","description":"Selects a spot (EPPP-weighted, revisit spacing, spaced-repetition due) and an UNUSED variant, shuffles the options and stores the key position server-side. If an item is still open it is returned again with resumed=true. The response never contains the keyed answer, the concept name or the source.\n\nRequired scope: teach.\n\nReturns: Item {item_id,stem,options:[{letter,text}],domain,knowledge_statement,kind,attempt_no,resumed,selection_reason}","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domains":{"description":"Override the session's domain filter for this pick","maxItems":8,"type":"array","items":{"type":"integer","minimum":1,"maximum":8}},"strategy":{"default":"auto","description":"auto = revisit spaced learning/missed spots, sometimes due-for-review, otherwise EPPP-weighted","type":"string","enum":["auto","weighted","due","revisit"]},"spot_id":{"description":"Force a specific spot (e.g. 'go deeper')","type":"string"}}}}}},"responses":{"201":{"description":"Item {item_id,stem,options:[{letter,text}],domain,knowledge_statement,kind,attempt_no,resumed,selection_reason}","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Item {item_id,stem,options:[{letter,text}],domain,knowledge_statement,kind,attempt_no,resumed,selection_reason}"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sessions/{sessionId}/items/{itemId}":{"get":{"operationId":"getItem","tags":["Teach"],"summary":"Re-read an item (the 'repeat' voice command)","description":"Required scope: teach.\n\nReturns: Item","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"Item","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Item"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sessions/{sessionId}/items/{itemId}/answer":{"post":{"operationId":"submitAnswer","tags":["Teach"],"summary":"Commit the learner's answer with the tutor's classification","description":"Multiple-choice items: send choice (letter) and, when correct, reasoning=sound|shaky. The server grades against the stored key and returns classification (correct_sound | correct_shaky | incorrect | no_answer), the new mastery state, and next_step. Agent-judged formats (fill_gap, teach_back): send outcome. In exam mode feedback is withheld.\n\nRequired scope: teach.\n\nReturns: { correct, classification, mastery_state, next_step, retries_remaining, reveal_allowed, keyed_answer|null, misconception, chain_ended }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"choice":{"description":"Displayed letter the learner committed to; null = no answer / 'I don't know'","anyOf":[{"type":"string","pattern":"^\\s*\\(?[A-Da-d]\\)?[.):]?\\s*$"},{"type":"null"}]},"reasoning":{"description":"Tutor's judgement of a CORRECT answer. Default sound. 'shaky' = right option but hesitant or flawed reasoning","type":"string","enum":["sound","shaky"]},"outcome":{"description":"Agent-judged formats only (fill_gap, teach_back)","type":"string","enum":["correct_sound","correct_shaky","incorrect","no_answer"]},"note":{"type":"string","maxLength":1000},"duration_ms":{"type":"integer","minimum":0,"maximum":3600000}}}}}},"responses":{"201":{"description":"{ correct, classification, mastery_state, next_step, retries_remaining, reveal_allowed, keyed_answer|null, misconception, chain_ended }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ correct, classification, mastery_state, next_step, retries_remaining, reveal_allowed, keyed_answer|null, misconception, chain_ended }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sessions/{sessionId}/items/{itemId}/skip":{"post":{"operationId":"skipItem","tags":["Teach"],"summary":"Skip: mark the spot missed and come back later (key not revealed)","description":"Required scope: teach.\n\nReturns: { skipped: true, mastery_state }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"201":{"description":"{ skipped: true, mastery_state }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ skipped: true, mastery_state }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sessions/{sessionId}/items/{itemId}/retry-material":{"get":{"operationId":"getRetryMaterial","tags":["Teach"],"summary":"Hints and retry-format material after an incorrect answer","description":"Only available after an incorrect / unanswered attempt. Returns hint ladder, fill-the-gap prompt with the expected answer, teach-back prompt with source-backed key points, and how to request variant/contrast retries.\n\nRequired scope: teach.\n\nReturns: { hints, formats:{variant,contrast,fill_gap,teach_back}, retries_remaining, variants_remaining }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ hints, formats:{variant,contrast,fill_gap,teach_back}, retries_remaining, variants_remaining }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ hints, formats:{variant,contrast,fill_gap,teach_back}, retries_remaining, variants_remaining }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sessions/{sessionId}/retry":{"post":{"operationId":"createRetry","tags":["Teach"],"summary":"Create retry 1 (new variant) or retry 2 (changed format)","description":"Up to two retries per concept. 'variant' serves a different unused scenario with options reshuffled. If every variant was used, the server falls back to the contrast format (or 409 if format=variant was explicit).\n\nRequired scope: teach.\n\nReturns: Item (kind variant|contrast|fill_gap|teach_back)","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"parent_item_id":{"type":"string"},"format":{"description":"Default: retry 1 = variant, retry 2 = contrast","type":"string","enum":["variant","contrast","fill_gap","teach_back"]}},"required":["parent_item_id"]}}}},"responses":{"201":{"description":"Item (kind variant|contrast|fill_gap|teach_back)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Item (kind variant|contrast|fill_gap|teach_back)"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/spots/{spotId}/explanation":{"get":{"operationId":"getSpotExplanation","tags":["Teach"],"summary":"Three-level explanation material for a spot/variant (after reveal)","description":"Exact (source, verbatim excerpt, rationale, why distractors are wrong), big picture (domain, knowledge statement, related concepts), 5th-grader source material, memory-hook slot and accuracy reminders. 409 reveal_not_allowed until the learner has answered correctly, exhausted the retries, or the session ended.\n\nRequired scope: teach.\n\nReturns: Explanation material","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"spotId","in":"path","required":true,"schema":{"type":"string"}},{"name":"variant","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":20}},{"name":"item_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"Explanation material","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Explanation material"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/spots/{spotId}/mnemonic":{"put":{"operationId":"saveMnemonic","tags":["Teach"],"summary":"Store the mnemonic the tutor gave, so summaries can repeat it","description":"Required scope: teach.\n\nReturns: { spot_id, mnemonic }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"spotId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mnemonic":{"type":"string","minLength":3,"maxLength":600}},"required":["mnemonic"]}}}},"responses":{"200":{"description":"{ spot_id, mnemonic }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ spot_id, mnemonic }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sessions/{sessionId}/summary":{"get":{"operationId":"getSessionSummary","tags":["Sessions"],"summary":"Session summary: first-try accuracy, strongest/weakest domains, concepts to review","description":"Mirrors 'Ending a session'. Percentages are practice percentages only; they do not convert to the ASPPB 200-800 scaled score. Also usable mid-session for 'How am I doing?'.\n\nRequired scope: teach.\n\nReturns: { first_try:{correct,total,percent}, strongest_domain, weakest_domain, concepts_to_review[<=3], next_session_suggestion, practice_notice }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ first_try:{correct,total,percent}, strongest_domain, weakest_domain, concepts_to_review[<=3], next_session_suggestion, practice_notice }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ first_try:{correct,total,percent}, strongest_domain, weakest_domain, concepts_to_review[<=3], next_session_suggestion, practice_notice }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sessions/{sessionId}/revisit":{"get":{"operationId":"getRevisitList","tags":["Sessions"],"summary":"Spots to bring back later this session (learning / missed) and flagged items","description":"Required scope: teach.\n\nReturns: { revisit:[{spot_id,state,ready_now,unused_variants,...}], flagged:[...] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ revisit:[{spot_id,state,ready_now,unused_variants,...}], flagged:[...] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ revisit:[{spot_id,state,ready_now,unused_variants,...}], flagged:[...] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sessions/{sessionId}/review":{"get":{"operationId":"getSessionReview","tags":["Sessions"],"summary":"After the session ends: missed items with full explanation material","description":"The exam-mode review phase. Requires the session to have ended.\n\nRequired scope: teach.\n\nReturns: { missed:[{stem,classification,explanation}] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ missed:[{stem,classification,explanation}] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ missed:[{stem,classification,explanation}] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sessions/{sessionId}/flags":{"post":{"operationId":"flagItem","tags":["Sessions"],"summary":"Flag an item as flawed or to revisit","description":"Required scope: teach.\n\nReturns: { flag_id, spot_id, reason }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"spot_id":{"type":"string"},"item_id":{"type":"string"},"reason":{"type":"string","enum":["flawed_item","revisit","other"]},"note":{"type":"string","maxLength":1000}},"required":["spot_id","reason"]}}}},"responses":{"201":{"description":"{ flag_id, spot_id, reason }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ flag_id, spot_id, reason }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/flags":{"get":{"operationId":"listFlags","tags":["Progress"],"summary":"List the learner's open flags (flawed items, revisit)","description":"Required scope: progress:read.\n\nReturns: { flags: [...] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"include_resolved","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ flags: [...] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ flags: [...] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/spots":{"post":{"operationId":"createCustomSpot","tags":["Exams"],"summary":"Add a custom spot (same schema as a bank Spot)","description":"o[0] is the keyed answer; exactly four distinct options; 1-12 variants; kn must belong to domain d. Never overrides a bank spot.\n\nRequired scope: exams:write.\n\nReturns: The stored spot (id generated if omitted)","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_.-]{2,63}$"},"d":{"type":"integer","minimum":1,"maximum":8},"kn":{"type":"string","pattern":"^KN\\d+$"},"concept":{"type":"string","minLength":1,"maxLength":300},"src":{"type":"string","minLength":1,"maxLength":300},"url":{"type":"string","format":"uri"},"ex":{"type":"string","minLength":1,"maxLength":600},"o":{"minItems":4,"maxItems":4,"type":"array","items":{"type":"string","minLength":1,"maxLength":500}},"why":{"type":"string","minLength":1,"maxLength":2000},"v":{"minItems":1,"maxItems":12,"type":"array","items":{"type":"object","properties":{"k":{"default":0,"type":"integer","minimum":0,"maximum":3},"s":{"type":"string","minLength":20,"maxLength":2000},"r":{"type":"string","minLength":5,"maxLength":1500}},"required":["s","r"]}}},"required":["d","kn","concept","src","url","ex","o","why","v"]}}}},"responses":{"201":{"description":"The stored spot (id generated if omitted)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"The stored spot (id generated if omitted)"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/exams":{"post":{"operationId":"createExam","tags":["Exams"],"summary":"Create an exam (settings: timed, shuffle, domain composition)","description":"Required scope: exams:write.\n\nReturns: Exam {exam_id,title,status,settings,question_count,questions[]}","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"settings":{"type":"object","properties":{"timed":{"default":false,"type":"boolean"},"time_limit_minutes":{"type":"integer","minimum":1,"maximum":600},"shuffle_options":{"default":true,"type":"boolean"},"shuffle_questions":{"default":false,"type":"boolean"},"domain_composition":{"type":"object","properties":{"mode":{"type":"string","enum":["eppp_weights","equal","custom"]},"counts":{"type":"object","propertyNames":{"type":"string","pattern":"^[1-8]$"},"additionalProperties":{"type":"integer","minimum":0,"maximum":500}},"domains":{"minItems":1,"type":"array","items":{"type":"integer","minimum":1,"maximum":8}}},"required":["mode"]},"target_percent":{"type":"number","minimum":0,"maximum":100}}},"learner_id":{"description":"Restrict the exam to one learner","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"status":{"type":"string","enum":["draft","published"]}},"required":["title"]}}}},"responses":{"201":{"description":"Exam {exam_id,title,status,settings,question_count,questions[]}","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Exam {exam_id,title,status,settings,question_count,questions[]}"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listExams","tags":["Exams"],"summary":"List exams (filtered to the learner when X-Learner-Id is given)","description":"Required scope: exams:read.\n\nReturns: { exams: [...] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"X-Learner-Id","in":"header","required":false,"description":"Optional learner context (filters results).","schema":{"type":"string"}}],"responses":{"200":{"description":"{ exams: [...] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ exams: [...] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/exams/{examId}":{"get":{"operationId":"getExam","tags":["Exams"],"summary":"Get an exam. Question list only with exams:write","description":"Required scope: exams:read.\n\nReturns: Exam","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"examId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Optional learner context (filters results).","schema":{"type":"string"}}],"responses":{"200":{"description":"Exam","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Exam"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateExam","tags":["Exams"],"summary":"Update title, settings or status (draft/published)","description":"Required scope: exams:write.\n\nReturns: Exam","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"examId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"settings":{"description":"Merged over existing settings, then validated","type":"object","properties":{},"additionalProperties":{}},"status":{"type":"string","enum":["draft","published"]}}}}}},"responses":{"200":{"description":"Exam","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Exam"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/exams/{examId}/questions":{"post":{"operationId":"addExamQuestions","tags":["Exams"],"summary":"Add bank questions (spot_id[, variant_index]) or custom questions","description":"Required scope: exams:write.\n\nReturns: { added:[...], question_count }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"examId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"questions":{"minItems":1,"maxItems":200,"type":"array","items":{"anyOf":[{"type":"object","properties":{"spot_id":{"type":"string"},"variant_index":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["spot_id"]},{"type":"object","properties":{"custom":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_.-]{2,63}$"},"d":{"type":"integer","minimum":1,"maximum":8},"kn":{"type":"string","pattern":"^KN\\d+$"},"concept":{"type":"string","minLength":1,"maxLength":300},"src":{"type":"string","minLength":1,"maxLength":300},"url":{"type":"string","format":"uri"},"ex":{"type":"string","minLength":1,"maxLength":600},"o":{"minItems":4,"maxItems":4,"type":"array","items":{"type":"string","minLength":1,"maxLength":500}},"why":{"type":"string","minLength":1,"maxLength":2000},"v":{"minItems":1,"maxItems":12,"type":"array","items":{"type":"object","properties":{"k":{"default":0,"type":"integer","minimum":0,"maximum":3},"s":{"type":"string","minLength":20,"maxLength":2000},"r":{"type":"string","minLength":5,"maxLength":1500}},"required":["s","r"]}}},"required":["d","kn","concept","src","url","ex","o","why","v"]},"variant_index":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["custom"]}]}}},"required":["questions"]}}}},"responses":{"201":{"description":"{ added:[...], question_count }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ added:[...], question_count }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/exams/{examId}/questions/{questionId}":{"delete":{"operationId":"removeExamQuestion","tags":["Exams"],"summary":"Remove a question","description":"Required scope: exams:write.\n\nReturns: { removed, question_count }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"examId","in":"path","required":true,"schema":{"type":"string"}},{"name":"questionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ removed, question_count }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ removed, question_count }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/exams/{examId}/generate":{"post":{"operationId":"generateExamQuestions","tags":["Exams"],"summary":"Fill the exam from the catalog by domain composition (EPPP weights, equal, custom counts)","description":"Required scope: exams:write.\n\nReturns: { added, requested_by_domain, shortfall_by_domain, warning }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"examId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer","minimum":1,"maximum":500},"composition":{"description":"Defaults to the exam's settings.domain_composition, else EPPP weights","type":"object","properties":{"mode":{"type":"string","enum":["eppp_weights","equal","custom"]},"counts":{"type":"object","propertyNames":{"type":"string","pattern":"^[1-8]$"},"additionalProperties":{"type":"integer","minimum":0,"maximum":500}},"domains":{"minItems":1,"type":"array","items":{"type":"integer","minimum":1,"maximum":8}}},"required":["mode"]}}}}}},"responses":{"201":{"description":"{ added, requested_by_domain, shortfall_by_domain, warning }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ added, requested_by_domain, shortfall_by_domain, warning }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/exams/{examId}/attempts":{"post":{"operationId":"startExamAttempt","tags":["Exams"],"summary":"Start an attempt (options shuffled, keys withheld, deadline if timed)","description":"Required scope: exams:take.\n\nReturns: Attempt {attempt_id,deadline_at,questions:[{position,stem,options}]}","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"examId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"201":{"description":"Attempt {attempt_id,deadline_at,questions:[{position,stem,options}]}","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Attempt {attempt_id,deadline_at,questions:[{position,stem,options}]}"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/exam-attempts":{"get":{"operationId":"listExamAttempts","tags":["Exams"],"summary":"List the learner's attempts and results","description":"Required scope: exams:take.\n\nReturns: { attempts: [...] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"exam_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ attempts: [...] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ attempts: [...] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/exam-attempts/{attemptId}":{"get":{"operationId":"getExamAttempt","tags":["Exams"],"summary":"Attempt state, or results with per-domain breakdown once finished","description":"Required scope: exams:take.\n\nReturns: Attempt or result","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"Attempt or result","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Attempt or result"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/exam-attempts/{attemptId}/answers":{"put":{"operationId":"saveExamAnswers","tags":["Exams"],"summary":"Save answers (can be changed until finish)","description":"Required scope: exams:take.\n\nReturns: { saved, answered_count, seconds_remaining }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"answers":{"minItems":1,"maxItems":500,"type":"array","items":{"type":"object","properties":{"position":{"type":"integer","minimum":1,"maximum":9007199254740991},"choice":{"anyOf":[{"type":"string","pattern":"^\\s*\\(?[A-Da-d]\\)?[.):]?\\s*$"},{"type":"null"}]}},"required":["position","choice"]}}},"required":["answers"]}}}},"responses":{"200":{"description":"{ saved, answered_count, seconds_remaining }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ saved, answered_count, seconds_remaining }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/exam-attempts/{attemptId}/finish":{"post":{"operationId":"finishExamAttempt","tags":["Exams"],"summary":"Finish and grade automatically; returns results","description":"Practice percentage only. Unanswered questions count as incorrect. Includes per-domain and per-knowledge-statement breakdown and a per-question review.\n\nRequired scope: exams:take.\n\nReturns: { practice_percent, correct, total, by_domain[], by_knowledge_statement[], review[] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ practice_percent, correct, total, by_domain[], by_knowledge_statement[], review[] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ practice_percent, correct, total, by_domain[], by_knowledge_statement[], review[] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/homework":{"post":{"operationId":"createHomework","tags":["Homework"],"summary":"Create a homework assignment for the learner (multiple-choice and free-text items)","description":"Required scope: homework:write.\n\nReturns: Homework with items (keys withheld)","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200},"instructions":{"type":"string","maxLength":4000},"due_at":{"description":"Epoch milliseconds","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"items":{"minItems":1,"maxItems":100,"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"multiple_choice"},"spot_id":{"type":"string"},"variant_index":{"type":"integer","minimum":0,"maximum":9007199254740991},"points":{"default":1,"type":"number","minimum":0,"maximum":1000}},"required":["type","spot_id"]},{"type":"object","properties":{"type":{"type":"string","const":"multiple_choice"},"custom":{"type":"object","properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_.-]{2,63}$"},"d":{"type":"integer","minimum":1,"maximum":8},"kn":{"type":"string","pattern":"^KN\\d+$"},"concept":{"type":"string","minLength":1,"maxLength":300},"src":{"type":"string","minLength":1,"maxLength":300},"url":{"type":"string","format":"uri"},"ex":{"type":"string","minLength":1,"maxLength":600},"o":{"minItems":4,"maxItems":4,"type":"array","items":{"type":"string","minLength":1,"maxLength":500}},"why":{"type":"string","minLength":1,"maxLength":2000},"v":{"minItems":1,"maxItems":12,"type":"array","items":{"type":"object","properties":{"k":{"default":0,"type":"integer","minimum":0,"maximum":3},"s":{"type":"string","minLength":20,"maxLength":2000},"r":{"type":"string","minLength":5,"maxLength":1500}},"required":["s","r"]}}},"required":["d","kn","concept","src","url","ex","o","why","v"]},"variant_index":{"type":"integer","minimum":0,"maximum":9007199254740991},"points":{"default":1,"type":"number","minimum":0,"maximum":1000}},"required":["type","custom"]},{"type":"object","properties":{"type":{"type":"string","const":"free_text"},"prompt":{"type":"string","minLength":5,"maxLength":4000},"rubric":{"maxItems":30,"type":"array","items":{"type":"object","properties":{"criterion":{"type":"string","minLength":1,"maxLength":300},"points":{"type":"number","minimum":0,"maximum":1000}},"required":["criterion","points"]}},"model_answer":{"type":"string","maxLength":6000},"points":{"default":1,"type":"number","minimum":0,"maximum":1000}},"required":["type","prompt"]}]}}},"required":["title","items"]}}}},"responses":{"201":{"description":"Homework with items (keys withheld)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Homework with items (keys withheld)"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listHomework","tags":["Homework"],"summary":"List assignments and statuses","description":"Required scope: homework:read.\n\nReturns: { homework: [{homework_id,title,status,overdue,score,percent,pending_free_text}] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["assigned","submitted","graded"]}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ homework: [{homework_id,title,status,overdue,score,percent,pending_free_text}] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ homework: [{homework_id,title,status,overdue,score,percent,pending_free_text}] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/homework/{homeworkId}":{"get":{"operationId":"getHomework","tags":["Homework"],"summary":"Get an assignment (model answers only for graders)","description":"Required scope: homework:read.\n\nReturns: Homework","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"homeworkId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"Homework","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Homework"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/homework/{homeworkId}/submit":{"post":{"operationId":"submitHomework","tags":["Homework"],"summary":"Submit answers. Multiple choice is graded now; free text waits for a grade","description":"Required scope: homework:submit.\n\nReturns: Homework with submission","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"homeworkId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"answers":{"minItems":1,"maxItems":200,"type":"array","items":{"type":"object","properties":{"item_id":{"type":"string"},"choice":{"type":["string","null"]},"text":{"type":"string","maxLength":10000}},"required":["item_id"]}}},"required":["answers"]}}}},"responses":{"201":{"description":"Homework with submission","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Homework with submission"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/homework/{homeworkId}/submission":{"get":{"operationId":"getHomeworkSubmission","tags":["Homework"],"summary":"Get the submission with scores and feedback","description":"Required scope: homework:read.\n\nReturns: Submission","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"homeworkId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"Submission","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Submission"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/homework/{homeworkId}/grade":{"post":{"operationId":"gradeHomework","tags":["Homework"],"summary":"Post the agent's rubric-based score and feedback for free-text answers","description":"No LLM runs on the server. The calling agent reads the answer (see pendingGrading), grades it against the rubric/model answer, and posts score (0..max_points), feedback and optional per-criterion rubric_scores. Multiple-choice items are graded automatically and rejected here.\n\nRequired scope: homework:grade.\n\nReturns: Homework with graded submission","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"homeworkId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"grades":{"minItems":1,"maxItems":200,"type":"array","items":{"type":"object","properties":{"item_id":{"type":"string"},"score":{"type":"number","minimum":0},"feedback":{"type":"string","minLength":1,"maxLength":5000},"rubric_scores":{"maxItems":30,"type":"array","items":{"type":"object","properties":{"criterion":{"type":"string","maxLength":300},"points":{"type":"number","minimum":0},"comment":{"type":"string","maxLength":1000}},"required":["criterion","points"]}}},"required":["item_id","score","feedback"]}},"overall_feedback":{"type":"string","maxLength":5000}},"required":["grades"]}}}},"responses":{"200":{"description":"Homework with graded submission","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Homework with graded submission"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/grading/pending":{"get":{"operationId":"getPendingGrading","tags":["Homework"],"summary":"Free-text answers awaiting a grade, with prompt, rubric and model answer","description":"Required scope: homework:grade.\n\nReturns: { pending: [{homework_id,item_id,prompt,text,rubric,model_answer,max_points}] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ pending: [{homework_id,item_id,prompt,text,rubric,model_answer,max_points}] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ pending: [{homework_id,item_id,prompt,text,rubric,model_answer,max_points}] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/progress/overview":{"get":{"operationId":"getProgressOverview","tags":["Progress"],"summary":"Overall metrics: practice %, time spent, streaks, mastery counts, due count","description":"Required scope: progress:read.\n\nReturns: Overview","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"Overview","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"Overview"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/progress/domains":{"get":{"operationId":"getDomainProgress","tags":["Progress"],"summary":"Per-domain mastery and accuracy vs EPPP weight","description":"Required scope: progress:read.\n\nReturns: { domains: [...] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ domains: [...] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ domains: [...] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/progress/knowledge-statements":{"get":{"operationId":"getKnowledgeStatementProgress","tags":["Progress"],"summary":"Per-knowledge-statement mastery and accuracy","description":"Required scope: progress:read.\n\nReturns: { knowledge_statements: [...] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ knowledge_statements: [...] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ knowledge_statements: [...] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/progress/spots":{"get":{"operationId":"getSpotMastery","tags":["Progress"],"summary":"Mastery state per spot (mastered / learning / missed / new)","description":"Required scope: progress:read.\n\nReturns: { spots: [...] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"type":"string","enum":["learning","missed","mastered","new"]}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ spots: [...] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ spots: [...] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/progress/trend":{"get":{"operationId":"getTrend","tags":["Progress"],"summary":"First-try accuracy over time (per day, with 7-day rolling)","description":"Required scope: progress:read.\n\nReturns: { days: [{date,attempts,correct,percent,rolling7}] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":365}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ days: [{date,attempts,correct,percent,rolling7}] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ days: [{date,attempts,correct,percent,rolling7}] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/progress/weak-areas":{"get":{"operationId":"getWeakAreas","tags":["Progress"],"summary":"Weak and strong areas, ranked (weighted by EPPP weight)","description":"Required scope: progress:read.\n\nReturns: { weakest_domains, weakest_knowledge_statements, weakest_spots, strongest_* }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ weakest_domains, weakest_knowledge_statements, weakest_spots, strongest_* }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ weakest_domains, weakest_knowledge_statements, weakest_spots, strongest_* }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/progress/tips":{"get":{"operationId":"getStudyTips","tags":["Progress"],"summary":"Rule-based study tips from weak areas, citing each spot's source","description":"Generated from the learner's data and the bank's own source, excerpt and distractor note. Nothing is invented.\n\nRequired scope: progress:read.\n\nReturns: { tips: [{title,detail,actions,source}] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ tips: [{title,detail,actions,source}] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ tips: [{title,detail,actions,source}] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/progress/review-queue":{"get":{"operationId":"getReviewQueue","tags":["Progress"],"summary":"Spaced-repetition due list","description":"Required scope: progress:read.\n\nReturns: { due: [...] }","security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"X-Learner-Id","in":"header","required":false,"description":"Learner the agent acts for. Required unless the API key is bound to a learner.","schema":{"type":"string"}}],"responses":{"200":{"description":"{ due: [...] }","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"description":"{ due: [...] }"}}}}}},"400":{"description":"validation_error, bad_request, invalid_choice, learner_required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthorized: missing, invalid, revoked or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"forbidden (missing scope) or learner_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found, learner_not_found, no_item_available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"State conflict, e.g. reveal_not_allowed, already_answered, variants_exhausted, session_ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key: eppp_<id>.<secret>"},"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key"}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","examples":["validation_error"]},"message":{"type":"string"},"details":{}}}}}}}}