Files
classroomio/prd/public-api

name, overview, todos, isProject
name overview todos isProject
Public API with Keys Build a versioned public API (v1) authenticated via shared organization-scoped automation keys reused by MCP and Public API, with course CRUD, course export, student/audience CRUD endpoints, plus a dashboard Automation page with MCP, Zapier, and API tabs.
id content status
db-schema Add `api_key` table to schema.ts, update relations, generate migration pending
id content status
db-queries Create api-key query functions (create, getByHash, getByOrg, revoke, updateLastUsed) pending
id content status
api-key-middleware Build apiKeyMiddleware that authenticates via Bearer token and sets orgId in context pending
id content status
validation-schemas Add Zod schemas for shared automation key management and public API input validation pending
id content status
api-key-service Create api-key service (key generation, hashing, CRUD) pending
id content status
api-key-routes Create session-authenticated automation key management routes reused by MCP and Public API pending
id content status
v1-course-service Create v1 course service (list, get, create, update, delete, export, students) pending
id content status
v1-audience-service Create v1 audience service (list, get, add, update, remove) pending
id content status
v1-routes Create versioned v1 router with course and audience sub-routers, register in app.ts pending
id content status
tests Write tests for api-key, v1/courses, v1/audience endpoints using Hono app.request() pending
id content status
translations Add automation-related translation keys to en.json pending
id content status
sidebar-nav Add Automation nav item to org-navigation.ts pending
id content status
automation-page Create automation page with Tabs (MCP/Zapier/API), admin-only controls, and key setup flows pending
id content status
automation-api-class Create frontend API class for api-key management pending
id content status
build-verify Build API package, verify no lint errors, run tests pending
id content status
openapi-contracts Ensure all new public API routes are included in OpenAPI generation (route metadata + schemas) pending
id content status
openapi-publish Regenerate and upload latest OpenAPI spec via `upload-openapi-spec.ts` pending
id content status
docs-openapi-integration Integrate OpenAPI docs page in `apps/docs` using Fumadocs OpenAPI integration pending
id content status
docs-openapi-nav Add `API` docs navigation entry/page in `apps/docs/content/docs/meta.json` pending
false

Public API with Organization-Scoped API Keys

Architecture Overview

flowchart TB
    subgraph client [External Client]
        APIKey["API Key in Authorization header"]
    end
    subgraph api [API Server]
        V1Router["/v1 Router"]
        APIKeyMiddleware["apiKeyMiddleware"]
        CourseRoutes["Course Routes"]
        AudienceRoutes["Audience Routes"]
        APIKeyRoutes["API Key Management"]
    end
    subgraph db [Database]
        APIKeyTable["api_key table"]
        OrgTable["organization"]
        CourseTable["course / group"]
        AudienceTable["groupmember / profile"]
    end

    client -->|"Bearer cio_..."| V1Router
    V1Router --> APIKeyMiddleware
    APIKeyMiddleware -->|"validates + resolves orgId"| APIKeyTable
    APIKeyMiddleware --> CourseRoutes
    APIKeyMiddleware --> AudienceRoutes
    APIKeyRoutes -->|"session auth"| APIKeyTable

1. Database: api_key Table

Add a new table in packages/db/src/schema.ts:

export const apiKey = pgTable('api_key', {
  id: uuid('id').defaultRandom().primaryKey(),
  organizationId: uuid('organization_id').notNull().references(() => organization.id, { onDelete: 'cascade' }),
  keyHash: text('key_hash').notNull(),       // SHA-256 hash of the key
  keyPrefix: varchar('key_prefix', { length: 12 }).notNull(), // "cio_live_xxxx" for display
  name: varchar('name', { length: 255 }).notNull().default('Default'),
  lastUsedAt: timestamp('last_used_at'),
  expiresAt: timestamp('expires_at'),
  isRevoked: boolean('is_revoked').notNull().default(false),
  createdAt: timestamp('created_at').defaultNow().notNull(),
  createdBy: uuid('created_by').notNull().references(() => profile.id),
});
  • The raw key is only shown once on creation; we store a SHA-256 hash.
  • keyPrefix (e.g. cio_live_a1b2) lets users identify keys in the UI without exposing the secret.
  • DON'T GENERATE MIGRATION, when we run pnpm db push it will automatically apply the changes.

2. API Key Queries

New file: packages/db/src/queries/api-key/api-key.ts

Functions:

  • createApiKey(orgId, createdBy, name?) -- inserts row, returns id
  • getApiKeyByHash(hash) -- lookup for auth middleware
  • getApiKeysByOrganization(orgId) -- list keys (prefix, name, createdAt, lastUsedAt, isRevoked)
  • revokeApiKey(keyId, orgId) -- sets isRevoked = true
  • updateApiKeyLastUsed(keyId) -- stamps lastUsedAt

3. API Key Auth Middleware

New file: apps/api/src/middlewares/api-key.ts

  • Reads Authorization: Bearer cio_live_... header
  • SHA-256 hashes the key, looks up via getApiKeyByHash
  • Checks isRevoked, expiresAt
  • Sets c.set('orgId', key.organizationId) on context (same pattern as orgMemberMiddleware)
  • Returns 401 on invalid/expired/revoked key

4. Validation Schemas

New file: packages/utils/src/validation/api-key/api-key.ts

Schemas for API key management and public API input validation:

  • ZCreateApiKey -- { name?: string }
  • ZRevokeApiKeyParam -- { keyId: string (uuid) }
  • Public API schemas for course CRUD, audience CRUD, pagination, etc.

5. Public API Routes (Versioned)

Router structure

New directory: apps/api/src/routes/v1/

Files:

Register in apps/api/src/app.ts:

.route('/v1', v1Router)

All v1 routes use apiKeyMiddleware (not session auth).

Course Endpoints (/v1/courses)

Method Path Description
GET /v1/courses List all org courses (paginated)
POST /v1/courses Create a course
GET /v1/courses/:courseId Get course details
PUT /v1/courses/:courseId Update a course
DELETE /v1/courses/:courseId Soft-delete a course
GET /v1/courses/:courseId/export Export course content (lessons, exercises) as JSON
GET /v1/courses/:courseId/students List students enrolled in a course

Audience Endpoints (/v1/audience)

Method Path Description
GET /v1/audience List all org students (paginated)
POST /v1/audience Add a student to the org
GET /v1/audience/:studentId Get student details
PUT /v1/audience/:studentId Update student info
DELETE /v1/audience/:studentId Remove student from org

API Key Management (session-authenticated, not public API)

New file: apps/api/src/routes/api-key/api-key.ts

Method Path Description
POST /api-key Generate new API key (returns raw key once)
GET /api-key List API keys for the org
DELETE /api-key/:keyId Revoke an API key

Registered in app.ts as .route('/api-key', apiKeyRouter). Protected by authMiddleware + orgAdminMiddleware.

6. Services

7. Tests

New test files under apps/api/src/__tests__/:

  • api-key.test.ts -- API key creation, listing, revocation, auth flow
  • v1/courses.test.ts -- All course CRUD + export + students endpoints
  • v1/audience.test.ts -- All audience CRUD endpoints

Tests will use Hono's app.request() test helper (no HTTP server needed). Mock the DB layer with vi.mock().

Additionally, include OpenAPI contract coverage:

  • verify all /v1/* and /api-key/* public-facing endpoints are represented in generated spec.
  • ensure schema components are generated for request/response payloads.

8. Dashboard UI: Automation Page

Sidebar Navigation

Add an "Automation" nav item in apps/dashboard/src/lib/features/ui/navigation/org-navigation.ts, placed before Settings, with requiresAdmin: true:

{
  titleKey: 'org_navigation.automation',
  path: '/automation',
  icon: AutomationIcon, // use Zap or Workflow icon from lucide
  requiresAdmin: true,
  matchPattern: '^/org/[^/]+/automation(/.*)?$'
}

Route

New file: apps/dashboard/src/routes/org/[slug]/automation/+page.svelte

Page Structure

+----------------------------------------------+
| Automation                                    |
| Connect ClassroomIO with your tools and       |
| workflows.                                    |
+----------------------------------------------+
| [ API ]  [ Zapier ]  [ Webhooks ]             |
+----------------------------------------------+
|                                               |
|  (Tab content based on selection)             |
|                                               |
+----------------------------------------------+
  • Title: "Automation" with description text
  • Uses Tabs from @cio/ui/base/tabs
  • Three tabs: API, Zapier, Webhooks

API Tab Content

Free plan users: Show UpgradeBanner component (same pattern as other pages). Disable all controls.

Paid plan users:

  • API key display field (masked by default, e.g. cio_live_••••••••••••a1b2)
  • "Show" / "Hide" toggle button to reveal/mask the key
  • "Copy" button to copy to clipboard
  • "Regenerate" button (with confirmation) to create a new key and revoke the old one
  • "Read the Docs" button/link pointing to the API documentation URL

API Class

New file: apps/dashboard/src/lib/features/automation/api/automation.svelte.ts

Extends BaseApi, methods:

  • fetchApiKeys() -- GET /api-key
  • createApiKey() -- POST /api-key
  • revokeApiKey(keyId) -- DELETE /api-key/:keyId

Frontend Types

New file: apps/dashboard/src/lib/features/automation/utils/types.ts

Translations

Add keys to apps/dashboard/src/lib/utils/translations/en.json:

  • org_navigation.automation
  • automation.title, automation.description
  • automation.tabs.api, automation.tabs.zapier, automation.tabs.webhooks
  • automation.api.key_label, automation.api.copy, automation.api.regenerate, automation.api.read_docs
  • automation.api.upgrade_prompt
  • automation.zapier.coming_soon, automation.webhooks.coming_soon

9. Versioning Strategy

  • All public API routes live under /v1/. When v2 is needed, a new apps/api/src/routes/v2/ directory is created with its own router, registered as .route('/v2', v2Router).
  • The v1 router remains untouched, ensuring backward compatibility.
  • The API key middleware is version-agnostic and shared across all versions.

10. OpenAPI Spec Requirements

Route Annotation Requirements

All new public API routes in this PRD must be OpenAPI-documented:

  • add route metadata using existing Hono OpenAPI patterns (describeRoute, validators/schemas).
  • ensure request params/query/body and success/error response shapes are declared.
  • avoid undocumented routes under /v1/* and /api-key/*.

Spec Generation and Publishing

Use existing script in apps/api/scripts/upload-openapi-spec.ts:

  • generate spec from app routes.
  • save local output to apps/api/dist/openapi/openapi.json.
  • upload dated + latest specs to R2 (openapi/openapi-YYYY-MM-DD.json, openapi/openapi-latest.json).

Required update flow after public API changes:

  1. implement/adjust routes and schemas.
  2. run pnpm --filter @cio/api upload:openapi.
  3. verify openapi-latest.json contains new endpoints.

CI/Verification Gate

Add a verification step in this initiative to prevent stale API docs:

  • PR checklist requires successful OpenAPI generation for API changes.
  • include generated-spec validation in API CI for public API route changes.

11. Docs Integration (Fumadocs OpenAPI)

Integrate API docs in apps/docs following Fumadocs OpenAPI integration guidance:

  • reference: https://www.fumadocs.dev/docs/integrations/openapi
  • wire docs app to render OpenAPI spec from latest published source.

Target implementation tasks:

  1. Add required Fumadocs OpenAPI integration package(s) to apps/docs/package.json.
  2. Add a docs route/page named API at /api in apps/docs/src/routes (or equivalent docs content route).
  3. Configure the page to load openapi-latest.json URL (env-driven preferred, with production fallback).
  4. Add an API entry to docs navigation in apps/docs/content/docs/meta.json.
  5. Validate local docs build still passes with the OpenAPI page.

Suggested file touchpoints:

  • apps/docs/package.json
  • apps/docs/src/routes/* (new /api route) or apps/docs/content/docs/api.mdx
  • apps/docs/content/docs/meta.json
  • optional docs config/util files for OpenAPI URL env handling

12. Build and Verification Commands

  1. pnpm --filter @cio/api build
  2. pnpm --filter @cio/api upload:openapi
  3. pnpm --filter @cio/docs build
  4. Manual check: docs navigation shows API, open /api, and verify new endpoints appear.

Note: Follow AGENTS.md for code practices.