AI LearnOS API & Protocol Specification
This document provides the authoritative reference catalog of all public, authenticated, and internal HTTP API surfaces implemented in AI LearnOS (apps/web/src/app/api/**), organized according to the master specification (§45 Protocol Surfaces).
---
1. Protocol Overview & Conventions
- Protocol Version:
0.1(declared in@ailearnos/identity-protocolasPROTOCOL_VERSION = "0.1"). - Transport: HTTPS, JSON request/response bodies (except multipart form upload for source documents and URL-encoded webhook payloads from payment gateways).
- Content-Type:
application/json; charset=utf-8(unless otherwise noted). - Date/Time Formatting: ISO 8601 UTC strings (
YYYY-MM-DDTHH:mm:ss.sssZ). - Currency & Money Formatting: Integer minor units (e.g.,
33000= 330.00 TWD) per §44 money invariant. - Credits & Balances: Non-negative integer points per §33 credit economy invariant.
---
2. Standard Error Taxonomy
Every failed API call returns a standardized JSON error payload with an appropriate HTTP status code:
{
"error": "machine_readable_snake_case_code",
"message": "Human readable context (optional)",
"details": {}
}Standard Status Codes & Error Codes
| HTTP Status | Primary Error Code | Meaning / Invariant Trigger | |---|---|---| | 400 Bad Request | missing_param, invalid_payload, invalid_source_type, missing_product_key, invalid_assistance_kind | Missing required fields, schema validation failure, or malformed input. | | 401 Unauthorized | unauthorized, missing_auth_header, invalid_signature | Missing or invalid user session, expired session token, or HMAC mismatch. | | 403 Forbidden | forbidden, role_required, unauthorized_organization, FLAG_DISABLED | Session exists but lacks required role (requireRole), organization role (requireOrgRole), or feature flag is disabled. | | 404 Not Found | not_found, grant_expired_or_invalid, document_not_found, match_not_found | Resource does not exist, has expired, or is scoped away from the caller. | | 413 Payload Too Large | file_too_large | Uploaded document exceeds the 50MB file size limit (MAX_UPLOAD_BYTES). | | 429 Too Many Requests | rate_limited, rate_limit_exceeded | Client IP or user account exceeded rate limit quota; response includes Retry-After header. | | 500 Internal Server Error | internal_error, compile_failed, checkout_failed | Unhandled runtime exception; safe error message returned without leaking server stack traces. |
---
3. API Catalog by §45 Specification Group
3.1 Verification Surface
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/verify/[id]</code>
- Purpose: Public verification of credential, passport claim, or proof-of-skill. Open to external viewers, employers, and verifiers without login.
- Authentication: None (Public).
- Path Parameters:
id(string, required): The opaquepublicVerificationIdissued to the credential.- Response (200 OK):
``json { "protocolVersion": "0.1", "status": "valid", "verificationId": "pvi_01HJ...", "credentialId": "crd_01HJ...", "skillNodeId": "sk_node_ml_01", "skillName": "Transformer Attention Mechanisms", "levelClaimed": "L3_proficient", "kind": "proof_of_skill", "issuedAt": "2026-08-22T08:00:00.000Z", "expiresAt": null, "revokedAt": null, "revokeReason": null, "issuer": { "id": "iss_platform_01", "displayName": "AI LearnOS Verified Authority", "authorityStatus": "verified_issuer" }, "evidenceSummary": { "count": 4, "lastAt": "2026-08-22T07:45:00.000Z", "topSource": "real_world_challenge" }, "verifiedAt": "2026-08-22T10:00:00.000Z" } ``
- Error Responses:
400 Bad Request:{"error": "missing_id"}404 Not Found:{"error": "not_found", "status": "not_found"}
---
3.2 Student Educational Email Verification
Implements the Cloudflare Email Routing + SWOT inbound verification flow (§34, §48, and docs/architecture/student-email-verification.md).
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/student-verification</code>
- Purpose: Initiates a student verification session, generating an ephemeral 128-bit cryptographic token (stored as SHA-256) and a
mailto:pre-filled URL. - Authentication: Required (
currentUserId). - Rate Limit: 5 requests/hour, 10 requests/day per account, maximum 3 active pending sessions.
- Request Body: None.
- Response (201 Created):
``json { "id": "svs_01HJ9XYZ...", "mailtoUrl": "mailto:verify+TOKEN_STRING@verify.example.com?subject=Student%20Verification&body=...", "expiresAt": "2026-08-22T10:15:00.000Z" } ``
- Error Responses:
401 Unauthorized:{"error": "Unauthorized"}429 Too Many Requests:{"error": "Rate limit exceeded (5 requests/hour)"}
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/student-verification/[id]/status</code>
- Purpose: Polls verification status for a specific student verification session owned by the authenticated user.
- Authentication: Required (
currentUserId). - Path Parameters:
id(string, required): Session ID.- Response (200 OK):
``json { "sessionId": "svs_01HJ9XYZ...", "status": "verified", "expiresAt": "2026-08-22T10:15:00.000Z", "verification": { "level": "educational_email_verified", "institutionName": "National Taiwan University", "domain": "ntu.edu.tw", "verifiedAt": "2026-08-22T10:05:00.000Z", "expiresAt": "2027-08-22T10:05:00.000Z" } } ``
- Error Responses:
401 Unauthorized:{"error": "Unauthorized"}403 Forbidden:{"error": "Forbidden"}(user does not own the session)404 Not Found:{"error": "Session not found"}
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/student-verification/[id]/cancel</code>
- Purpose: Explicitly cancels an active pending student verification session.
- Authentication: Required (
currentUserId). - Response (200 OK):
{"success": true}
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/student-verification/[id]/retry</code>
- Purpose: Cancels the previous session and generates a fresh token and
mailto:link. - Authentication: Required (
currentUserId). - Response (201 Created): Same shape as
POST /api/student-verification.
---
3.3 Share Surface
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/share</code>
- Purpose: Generates a privacy-preserving, scoped, time-bounded or view-bounded Share Grant for a learner's passport.
- Authentication: Required (
currentUserId). - Request Body:
``json { "scope": { "fullPassport": false, "skillNodeIds": ["sk_node_ml_01", "sk_node_ml_02"] }, "expiresAt": "2026-09-22T00:00:00.000Z", "maxViews": 5 } ``
- Response (201 Created):
``json { "grant": { "id": "shg_01HJ...", "userId": "usr_01HJ...", "scope": { "fullPassport": false, "skillNodeIds": ["sk_node_ml_01", "sk_node_ml_02"] }, "expiresAt": "2026-09-22T00:00:00.000Z", "maxViews": 5, "viewCount": 0, "createdAt": "2026-08-22T10:00:00.000Z" } } ``
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/share</code>
- Purpose: Resolves a public share grant link. Atomically increments
viewCountand verifies that the grant is not expired, revoked, or exceeded. - Authentication: None (Public).
- Query Parameters:
id(string, required): The share grant ID.- Response (200 OK):
``json { "grantId": "shg_01HJ...", "learner": { "displayName": "Alex Chen", "avatarUrl": null }, "scope": { "fullPassport": false, "skillNodeIds": ["sk_node_ml_01"] }, "skills": [ { "skillNodeId": "sk_node_ml_01", "name": "Transformer Attention Mechanisms", "level": "L3_proficient", "evidenceCount": 4, "lastVerifiedAt": "2026-08-22T07:45:00.000Z" } ], "expiresAt": "2026-09-22T00:00:00.000Z", "viewsRemaining": 4 } ``
- Error Responses:
400 Bad Request:{"error": "missing_share_id"}404 Not Found:{"error": "grant_expired_or_invalid"}
---
3.4 Enterprise Verification Standard (EVS) Surface (§24.3, §40)
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/evs</code>
- Purpose: Learner issues a fixed-scope Enterprise Verification ID granting a specific organization permission to inspect specific skills.
- Authentication: Required (
currentUserId). - Request Body:
``json { "orgId": "org_enterprise_01", "skillNodeIds": ["sk_node_ml_01", "sk_node_stats_02"] } ``
- Response (201 Created):
``json { "evs": { "id": "evs_01HJ...", "userId": "usr_01HJ...", "organizationId": "org_enterprise_01", "scopedSkillNodeIds": ["sk_node_ml_01", "sk_node_stats_02"], "revokedAt": null, "createdAt": "2026-08-22T10:00:00.000Z" } } ``
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/evs/resolve</code>
- Purpose: Enterprise organization verifies an employee or candidate's EVS token.
- Authentication: Required + Role Gated (
requireRole("enterprise", "admin")). - Invariant: Unconditionally writes a
verification_access_logrecord recordingviewerOrgId,viewerUserId,evsId, and timestamp. Returns ONLY consented skills. - Request Body:
``json { "evsId": "evs_01HJ...", "orgId": "org_enterprise_01" } ``
- Response (200 OK):
``json { "valid": true, "evsId": "evs_01HJ...", "learnerId": "usr_01HJ...", "organizationId": "org_enterprise_01", "evsVersion": "1.0", "issuer": { "displayName": "National AI Skills Institute", "tier": "accredited" }, "scopedSkills": [ { "skillNodeId": "sk_node_ml_01", "skillName": "Transformer Attention Mechanisms", "level": "L3_proficient", "historicalPeakLevel": "L4_advanced", "evidenceSummary": { "count": 4, "lastAt": "2026-08-22T07:45:00.000Z", "topSource": "real_world_challenge" }, "confidenceBand": "high", "lastVerifiedAt": "2026-08-22T07:45:00.000Z", "credentialValidity": true } ], "resolvedAt": "2026-08-22T10:10:00.000Z" } ``
- EVS v1 Resolution Fields (§24.3):
evsVersion(string): Protocol version of the resolution payload, currently"1.0".issuer(object | null): Top-level credential issuer withdisplayNameandtier("platform"|"accredited"|"community");nullwhen the credential has no issuer.confidenceBand(string, per skill): Derived from the learner'sevidence_confidencevector —< 0.4="low",< 0.7="medium",>= 0.7="high".lastVerifiedAt(string | null, per skill): ISO timestamp fromlearner_skill_state.last_verified_at;nullwhen never verified.historicalPeakLevel(string, per skill): HighestSkillMasteryLevelever attained for the skill, reported alongside the currentlevel(§20 historical peak persists even when current state changes).credentialValidity(boolean, per skill):trueiff an active non-revoked proof-of-skill credential exists for that skill.- Error Responses:
401 Unauthorized:{"error": "unauthorized"}403 Forbidden:{"error": "unauthorized_organization", "valid": false}404 Not Found:{"error": "evs_not_found_or_revoked", "valid": false}
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/evs/revoke</code>
- Purpose: Learner revokes an issued EVS token immediately.
- Authentication: Required (
currentUserId). - Request Body:
{"evsId": "evs_01HJ..."} - Response (200 OK):
{"success": true, "evs": { ... }}
---
3.5 Opportunity & Network Surface (§31, §39)
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/network/leaderboard</code>
- Purpose: Reads capability rankings across Free and Premium leaderboard zones without pay-to-win manipulation.
- Authentication: None (Public read).
- Query Parameters:
zone(string, optional, default"free"):"free"|"premium".skillNodeId(string, optional): Filters rankings by specific skill.limit(integer, optional, default50, max100).- Response (200 OK):
``json { "entries": [ { "rank": 1, "userId": "usr_01HJ...", "displayName": "Alex Chen", "zone": "free", "totalMasteryPoints": 1450, "skillsMastered": 12, "topSkill": "Transformer Attention Mechanisms" } ] } ``
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/network/teams</code>
- Purpose: Learner creates, joins, or leaves a collaborative team.
- Authentication: Required (
currentUserId). - Request Body:
``json { "action": "create", "name": "Distributed Systems Study Group" } ``
- Response (200 OK):
{"team": { ... }}
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/network/teams</code>
- Purpose: Lists team rankings.
- Query Parameters:
zone("free"|"premium"),limit(default20).
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/network/matches</code>
- Purpose: Recomputes and returns privacy-preserving opportunity matches for the authenticated learner.
- Authentication: Required (
currentUserId). - Response (200 OK):
``json { "matches": [ { "id": "mat_01HJ...", "companyName": "Anthropic Partner Lab", "roleTitle": "AI Systems Engineer", "matchScore": 0.92, "matchedSkills": ["Transformer Attention Mechanisms", "Postgres Optimization"], "status": "pending_learner_release", "releasedAt": null } ] } ``
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/network/matches/[id]/release</code>
- Purpose: Learner explicitly consents to release their identity and verified passport to the hiring company for a specific matched opportunity.
- Authentication: Required (
currentUserId). - Path Parameters:
id(string, required): Match ID.- Response (200 OK):
{"match": { ... }}
---
3.6 Organization & Cohort Management (§3, §40)
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/org/cohorts</code>
- Purpose: Academy or enterprise manager creates a learner cohort for a course.
- Authentication: Required + Org Role (
requireOrgRole("manager", "owner")). - Request Body:
``json { "orgId": "org_academy_01", "courseId": "crs_01HJ...", "name": "2026 Fall LLM Engineering Cohort" } ``
- Response (201 Created):
{"cohort": { ... }}
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/org/cohorts?orgId=...</code>
- Purpose: Lists all cohorts within an organization.
- Authentication: Required + Org Role (
requireOrgRole("member", "manager", "owner")).
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/org/cohorts/[id]/members</code>
- Purpose: Adds a student or employee to a cohort.
- Authentication: Required + Org Role (
requireOrgRole("manager", "owner")). - Request Body:
``json { "userId": "usr_01HJ...", "email": "student@university.edu", "role": "student" } ``
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/org/cohorts/[id]/members</code>
- Purpose: Lists cohort roster and individual mastery progress.
- Authentication: Required + Org Role (
requireOrgRole("member", "manager", "owner")).
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">DELETE /api/org/cohorts/[id]/members</code>
- Purpose: Removes a member from a cohort.
- Authentication: Required + Org Role (
requireOrgRole("manager", "owner")). - Request Body:
{"userId": "usr_01HJ..."}
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/org/workforce-report?orgId=...</code>
- Purpose: Lists generated workforce capability reports and feature flag status.
- Authentication: Required + Org Role (
requireOrgRole("member", "manager", "owner")).
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/org/workforce-report</code>
- Purpose: Triggers generation of an enterprise workforce intelligence report (skill distribution, gap analysis, cohort velocity). Gated by feature flag
workforce_reports.enabled. - Authentication: Required + Org Role (
requireOrgRole("manager", "owner")). - Request Body:
{"orgId": "org_enterprise_01"} - Response (201 Created):
{"report": { ... }}
---
3.7 Learning Runtime & Interactive Activities (§7, §10, §13)
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/activities/[id]/assistance</code>
- Purpose: Requests structured tutor assistance during an activity (hint, explanation, remediation branch, or reveal solution). Enforces support-debt tracking.
- Authentication: Required (
requireUserId). - Path Parameters:
id(string, required): Activity ID.- Request Body:
``json { "kind": "hint", "attemptId": "att_01HJ...", "content": "I am unsure how the key-value projection dimensions align in multi-head attention." } ``
- Allowed Kinds:
"hint"|"explanation"|"remediation"|"solution". - Response (200 OK):
{"event": { "kind": "hint", "content": "..." }}
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/activities/[id]/attempts</code>
- Purpose: Records learner's answer attempt for an activity, calculates score, updates LearnerSkillState, and evaluates prerequisite diagnostic triggers.
- Authentication: Required (
requireUserId). - Path Parameters:
id(string, required): Activity ID.- Request Body:
``json { "response": { "selectedOptionIndex": 2 }, "correct": true, "score": 1.0 } ``
- Response (200 OK):
``json { "attempt": { "id": "att_01HJ...", "correct": true, "score": 1.0 }, "evidenceResult": { "skillNodeId": "sk_node_ml_01", "level": "L3_proficient", "mastered": true }, "diagnosis": null } ``
---
3.8 Compiler & Source Ingestion (§6, §8)
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/sources</code>
- Purpose: Ingests raw learning sources (PDF, EPUB, DOCX, Markdown, URL, or audio transcript). Extracts text, creates source spans with provenance tracking, and persists to configured storage.
- Authentication: Required (
requireUserId). - Content-Type:
multipart/form-data. - Form Fields:
file(File, required): Binary source file (max 50MB).sourceType(string, required):"pdf"|"epub"|"docx"|"md"|"url"|"audio_transcript".- Response (201 Created):
``json { "document": { "id": "src_doc_01HJ...", "title": "Attention Is All You Need.pdf", "sourceType": "pdf", "status": "ready", "chunkCount": 24 } } ``
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/sources</code>
- Purpose: Lists source documents uploaded by the authenticated user.
- Authentication: Required (
requireUserId).
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/compile</code>
- Purpose: Dispatches progressive course compilation pipeline for an ingested document. Uses Inngest Cloud workflow or local dev runner.
- Authentication: Required (
requireUserId). - Request Body:
{"documentId": "src_doc_01HJ..."} - Response (200 OK):
{"job": { "id": "cpj_01HJ...", "status": "pending" }}
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/compile</code>
- Purpose: Lists compilation jobs for the user.
- Authentication: Required (
requireUserId).
---
3.9 Billing & Subscriptions (§33, §44)
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/billing/plans</code>
- Purpose: Public catalog of active subscriptions and credit packs.
- Authentication: None (Public).
- Response (200 OK):
``json { "plans": [ { "key": "premium_monthly", "kind": "subscription", "displayNameKey": "billing.plans.premium_monthly", "priceMinor": 33000, "currency": "TWD", "monthlyPoints": 2000, "pointsGrant": null, "isActive": true }, { "key": "credits_pack_1000", "kind": "credit_pack", "displayNameKey": "billing.plans.credits_pack_1000", "priceMinor": 100000, "currency": "TWD", "monthlyPoints": null, "pointsGrant": 1000, "isActive": true } ] } ``
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/billing/checkout</code>
- Purpose: Initiates checkout for a subscription or credit pack. Routes currency automatically (TWD → NewebPay MPG, non-TWD → PayPal Orders).
- Authentication: Required (
currentUserId). - Request Body:
``json { "productKey": "premium_monthly", "successUrl": "https://example.com/billing?success=true", "cancelUrl": "https://example.com/billing?canceled=true" } ``
- Response (200 OK):
- For NewebPay: Form post parameters (
MerchantID,TradeInfo,TradeSha,Version,actionUrl). - For PayPal:
{ "checkoutUrl": "https://www.sandbox.paypal.com/checkoutnow?token=...", "orderId": "..." }.
---
3.10 Webhooks & Inbound Gateways
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/billing/webhook/newebpay</code>
- Purpose: Receives asynchronous MPG payment notification from NewebPay.
- Security: Verifies
TradeShaSHA-256 checksum withNEWEBPAY_HASH_KEYandNEWEBPAY_HASH_IV. Decrypts AES-256-CBCTradeInfo. Idempotent processing viawebhook_receipt. - Response (200 OK):
{"status": "SUCCESS", "duplicate": false}
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/billing/webhook/paypal</code>
- Purpose: Receives asynchronous order capture notifications from PayPal.
- Security: Inspects transmission headers (
paypal-transmission-id,paypal-transmission-sig), verifies certificate URL belongs to*.paypal.com, and computes HMAC whenPAYPAL_WEBHOOK_IDis set. - Response (200 OK):
{"status": "SUCCESS", "duplicate": false}
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/internal/student-verification/inbound</code>
- Purpose: Internal endpoint invoked by Cloudflare Email Worker when a student email is received at
verify+TOKEN@.... - Security: HMAC-SHA256 signature verification over the raw body via header
x-signature-sha256orx-signatureusingSTUDENT_VERIFY_HMAC_SECRET. - Payload:
{ "recipient": "verify+TOKEN@...", "envelopeSender": "student@ntu.edu.tw", "authResults": { "spf": "pass", "dkim": "pass", "dmarc": "pass" } }. - Response (200 OK):
{"success": true, "status": "verified", "level": "educational_email_verified"}
---
3.11 Admin & Operational Endpoints
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/admin/edu-domains</code>
- Purpose: Administrator manual upsert into the global
educational_domainregistry. - Authentication: Required + Admin Role (
requireRole("admin")). - Request Body:
``json { "domain": "ntu.edu.tw", "institutionName": "National Taiwan University", "countryCode": "TW", "institutionType": "university", "status": "trusted", "studentOnly": false } ``
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET|POST|PUT /api/inngest</code>
- Purpose: Inngest Cloud workflow webhook handler dispatching background compilation jobs (
compileJobFunction). - Authentication: Managed by Inngest SDK (
INNGEST_SIGNING_KEY/INNGEST_EVENT_KEY).
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET|POST /api/auth/[...all]</code>
- Purpose: Better-Auth core handler for password sessions, OAuth callbacks, account recovery, and two-factor TOTP verification.
- Security: Account recovery uses
/api/auth/recovery/requestwith multi-signal risk assessment; no magic-link endpoint is supported. 2FA usestwoFactor.enable,twoFactor.verifyTotp,twoFactor.verifyBackupCode, andtwoFactor.disable.