← 返回協定總覽

協定參考——API

即時呈現存放庫內的 docs/protocols/api.md。

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-protocol as PROTOCOL_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 opaque publicVerificationId issued 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 viewCount and 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_log record recording viewerOrgId, 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 with displayName and tier ("platform" | "accredited" | "community"); null when the credential has no issuer.
  • confidenceBand (string, per skill): Derived from the learner's evidence_confidence vector — < 0.4 = "low", < 0.7 = "medium", >= 0.7 = "high".
  • lastVerifiedAt (string | null, per skill): ISO timestamp from learner_skill_state.last_verified_at; null when never verified.
  • historicalPeakLevel (string, per skill): Highest SkillMasteryLevel ever attained for the skill, reported alongside the current level (§20 historical peak persists even when current state changes).
  • credentialValidity (boolean, per skill): true iff 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 &amp; 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, default 50, max 100).
  • 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 (default 20).
<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": { ... }}
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET|POST /api/org/opportunity-api-keys</code>
  • Purpose: Enterprise owners list or create a scoped machine credential for an external opportunity platform.
  • Authentication: Required session + organization owner role. Platform-admin bypass remains audited.
  • Create Body: {"organizationId":"org_...","label":"Production outcomes","expiresInDays":90}. Expiry is required and capped at 365 days.
  • Create Response (201 Created): Returns metadata plus an isk_v1_... bearer token exactly once with Cache-Control: no-store. Only a keyed digest and non-secret prefix are persisted.
  • Scope: Current keys carry only outcomes:write; they cannot query learner, match, Passport, or credential data.
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">DELETE /api/org/opportunity-api-keys/[id]?organizationId=...</code>
  • Purpose: Immediately revokes one organization-scoped integration key.
  • Authentication: Required session + organization owner role.
  • Response (200 OK): Returns non-secret key metadata with revokedAt.
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/opportunities/outcomes</code>
  • Purpose: Reports a real-world opportunity outcome, or records the learner's accept/decline decision.
  • Authentication: A report accepts either a session belonging to an owner/manager of the opportunity's organization or Authorization: Bearer isk_v1_... with outcomes:write. Accept/decline always requires the learner's session.
  • Provider Report Body:

``json { "action": "report", "opportunityId": "opp_...", "userId": "usr_...", "outcomeKind": "completed", "observedCapabilities": [{"skillNodeId": "skill_...", "confidence": 0.8}], "narrative": "Completed the placement" } ``

  • Privacy boundary: A provider can report only against its own opportunity and only after that learner released the match. A report is not Passport evidence until the learner explicitly accepts it.
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/opportunities/outcomes</code>
  • Purpose: Lists outcome reports awaiting the authenticated learner's decision.
  • Authentication: Required learner session; M2M keys have no read access.

---

3.6 Organization &amp; 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 LMS / LTI 1.3 Launch Surface (§174, Q57)

<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/lms/login</code>
  • Purpose: Handles LTI 1.3 third-party initiated login and redirects to the fixed authorization endpoint stored for the matching LMS registration.
  • Authentication: An existing IdeaStone session is required. This endpoint binds the one-time OIDC state to that account; it does not create a standalone LMS identity session.
  • Rate Limit: 120 initiations/hour per IdeaStone account.
  • Query Parameters: iss, login_hint, and target_link_uri are required; client_id, lti_deployment_id, and lti_message_hint are optional registration hints. target_link_uri must exactly equal the configured IdeaStone /api/lms/launch URL.
  • Response (302 Found): Redirects to the registration's stored HTTPS authorization URL. state and nonce contain at least 256 bits of randomness; only their SHA-256 digests are stored for ten minutes.
  • Security: Launch input cannot select an authorization endpoint, JWKS endpoint, or redirect target.
<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/lms/launch</code>
  • Purpose: Accepts an LTI 1.3 LtiResourceLinkRequest, verifies the platform assertion, and resolves the registered organization assignment and pre-linked IdeaStone identity.
  • Content-Type: application/x-www-form-urlencoded with required state and id_token fields (JWT is accepted as a compatibility alias for id_token).
  • Authentication: RS256 JWT from the registration's fixed HTTPS JWKS URL plus a matching unexpired, one-use state/nonce. The launch does not create or switch an IdeaStone account; any present IdeaStone session must match the account bound at login initiation.
  • Rate Limit: 600 form posts/hour per source IP, allowing campus NAT traffic while bounding JWT/JWKS work.
  • Response (200 OK): Returns only courseId, organizationId, externalRole, and assignmentLinkId, with Cache-Control: no-store.
  • Security: Exact issuer, audience/azp, deployment, message type, version, target URI, resource link, context, subject, token age, algorithm, and registered kid are enforced. JWKS retrieval rejects redirects and private/reserved destinations, pins a validated DNS address, and is bounded to five seconds and 256 KiB. Successful launches append lms.launch.succeeded to the audit trail without persisting the platform subject in audit detail.

---

3.8 Learning Runtime &amp; 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.9 Compiler &amp; 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.10 Billing &amp; 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.11 Webhooks &amp; 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 TradeSha SHA-256 checksum with NEWEBPAY_HASH_KEY and NEWEBPAY_HASH_IV. Decrypts AES-256-CBC TradeInfo. Idempotent processing via webhook_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 when PAYPAL_WEBHOOK_ID is 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-sha256 or x-signature using STUDENT_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.12 Admin &amp; Operational Endpoints

<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">POST /api/admin/modules/[slug]/recompile</code>
  • Purpose: Runs the high-value Module-engineering route for one deployed catalog Module and returns a schema-bound candidate for human review.
  • Authentication: Required + Admin Role (requireRole("admin")).
  • Billing and abuse control: Acquires the durable module.recompile paid-compute quota before provider invocation, charges Premium points through the AI Gateway, and always releases the concurrency lease.
  • Path Parameters:
  • slug (string, required): An exact slug from the deployed Module catalog.
  • Request Body:

``json { "objective": "Replace the abstract opening with a concrete accessible interaction.", "currentProps": { "conceptLabel": "Gradient descent" }, "locale": "en" } ``

  • Invariants: currentProps and provider-produced previewProps may contain only props and value types declared by that Module's generated catalog schema. Model output is untrusted and validated before return. The route never writes source code, publishes a Module, or mutates a course; every successful response has deployState: "review_required". Semantic audit records store the objective hash, not the objective text.
  • Response (200 OK): A candidate (summary, preview props, changes, implementation plan, verification checklist, and risks), provider invocation/cost metadata, and deployState: "review_required".
  • Error Responses: 400 malformed/schema-invalid request, 401 anonymous, 403 non-admin, 404 unknown Module, 402 insufficient points/budget, 429 paid-compute quota, 502 invalid provider output, or 503 disabled/unavailable provider route.
<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_domain registry.
  • 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/request with multi-signal risk assessment; no magic-link endpoint is supported. 2FA uses twoFactor.enable, twoFactor.verifyTotp, twoFactor.verifyBackupCode, and twoFactor.disable.

Certification assessment records

<code class="rounded bg-black/25 px-1.5 py-0.5 font-mono text-[13px]">GET /api/certification/sessions/{id}/report</code>
  • Authentication: Required; the signed-in user must own this exact session.

An unknown or other candidate's session returns the same 404 response.

  • Availability: Final certified/failed/invalidated decision and a used,

retired exam. Pending or reopened review returns 409; unused rejected exams do not release answers. No course purchase or membership check applies.

  • Response: { report, integrity }, version certification-score-report-v1.

Includes the candidate's actual answers, retired questions, public rubric and reference solutions, aggregate score/pass threshold, scheme context, final outcome, hashes and optional credential ID. Missing legacy data is flagged in completeness, never fabricated. The checksum is not an issuer signature.

  • Privacy: private, no-store, nosniff, rate limited. No generation

metadata, hidden deterministic checks, exam-resource capability URLs, raw identity media or proctoring details. Candidate responses can include their own private file links; sharing is voluntary and never automatically public.

  • Download: ?download=1 adds an attachment header with the fixed filename

certification-assessment-record.json.

  • Errors: 401 unauthenticated, 404 not owned/not found, 409 not final or

not retired, 429 rate limited, 500 inconsistent stored record. No corrupted record is partially returned.