feat: enhance API and middleware functionality: add public API routes, implement program and course member checks, and improve rate limiting for invite acceptance. Update OpenAPI spec generation to focus on public API endpoints and add new middleware for automation key scopes. Include new documentation for lesson AI features and public API tests.

This commit is contained in:
Rotimi Best
2026-04-12 08:58:41 +01:00
parent 5a8f50f67b
commit d6e812d08b
573 changed files with 31964 additions and 8708 deletions
+290 -276
View File
@@ -1,365 +1,379 @@
---
name: Public API with Keys
overview: 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.
name: Public API with Organization-Scoped Automation Keys
overview: Build the public API on top of the shipped organization automation foundation: the `/org/[slug]/automation` section already exists, MCP is live, `/automation/api` and `/automation/zapier` exist as placeholder pages, and org-scoped automation keys already exist in the backend. This PRD updates the remaining work to ship the actual public API experience and docs.
todos:
- id: db-schema
content: Add `api_key` table to schema.ts, update relations, generate migration
- id: confirm-existing-foundation
content: Confirm and preserve the shipped automation foundation (`Automation` nav, `/org/[slug]/automation` redirect, MCP page, organization automation key backend)
status: completed
- id: org-automation-foundation
content: Keep using `organization_api_key` and `/organization/automation/*` as the shared automation-key foundation for MCP, API, and Zapier
status: completed
- id: mcp-page-reference
content: Use the existing MCP implementation as the structural reference for the API page (route shell, server load, feature API class, feature page component, translations)
status: completed
- id: agents-guidance
content: Ensure all implementation in this initiative follows `AGENTS.md`
status: completed
- id: api-tab-page
content: Replace the placeholder `/org/[slug]/automation/api` page with a real API page that follows the MCP page pattern
status: pending
- id: db-queries
content: Create api-key query functions (create, getByHash, getByOrg, revoke, updateLastUsed)
- id: api-tab-server-load
content: Add `/org/[slug]/automation/api/+page.server.ts` to load API key and usage data using the existing organization automation endpoints
status: pending
- id: api-key-middleware
content: Build apiKeyMiddleware that authenticates via Bearer token and sets orgId in context
- id: api-tab-copy
content: Add API-specific translations and content for docs links, usage messaging, and key management states
status: pending
- id: validation-schemas
content: Add Zod schemas for shared automation key management and public API input validation
- id: v1-validation-schemas
content: Add public API input/output validation schemas on top of the existing automation-key schemas
status: pending
- id: api-key-service
content: Create api-key service (key generation, hashing, CRUD)
status: pending
- id: api-key-routes
content: Create session-authenticated automation key management routes reused by MCP and Public API
status: pending
- id: v1-course-service
content: Create v1 course service (list, get, create, update, delete, export, students)
status: pending
- id: v1-audience-service
content: Create v1 audience service (list, get, add, update, remove)
- id: v1-services
content: Create versioned public API services for courses and audience/students
status: pending
- id: v1-routes
content: Create versioned v1 router with course and audience sub-routers, register in app.ts
content: Create and register `/v1` public API routes authenticated by organization automation keys
status: pending
- id: public-api-scopes
content: Finalize the public API scope model and middleware requirements for `api` and `zapier` automation keys
status: pending
- id: tests
content: Write tests for api-key, v1/courses, v1/audience endpoints using Hono app.request()
status: pending
- id: translations
content: Add automation-related translation keys to en.json
status: pending
- id: sidebar-nav
content: Add Automation nav item to org-navigation.ts
status: pending
- id: automation-page
content: Create automation page with Tabs (MCP/Zapier/API), admin-only controls, and key setup flows
status: pending
- id: automation-api-class
content: Create frontend API class for api-key management
status: pending
- id: build-verify
content: Build API package, verify no lint errors, run tests
content: Add API tests for automation auth, `/v1` routes, and contract coverage
status: pending
- id: openapi-contracts
content: Ensure all new public API routes are included in OpenAPI generation (route metadata + schemas)
content: Ensure all public API routes are represented in generated OpenAPI output
status: pending
- id: openapi-publish
content: Regenerate and upload latest OpenAPI spec via `upload-openapi-spec.ts`
content: Regenerate and upload the latest OpenAPI spec via `upload-openapi-spec.ts`
status: pending
- id: docs-openapi-integration
content: Integrate OpenAPI docs page in `apps/docs` using Fumadocs OpenAPI integration
content: Add the public API docs experience in `apps/docs`
status: pending
- id: docs-openapi-nav
content: Add `API` docs navigation entry/page in `apps/docs/content/docs/meta.json`
- id: build-verify
content: Build and verify API and docs packages after implementation
status: pending
isProject: false
---
# Public API with Organization-Scoped API Keys
# Public API with Organization-Scoped Automation Keys
## Architecture Overview
## Goal
```mermaid
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
Ship a versioned public API on top of the existing organization automation platform. The current codebase already supports shared org-scoped automation keys and a dedicated Automation section in `/org`; this PRD now focuses on the remaining API product work rather than re-specifying the already-shipped foundation.
client -->|"Bearer cio_..."| V1Router
V1Router --> APIKeyMiddleware
APIKeyMiddleware -->|"validates + resolves orgId"| APIKeyTable
APIKeyMiddleware --> CourseRoutes
APIKeyMiddleware --> AudienceRoutes
APIKeyRoutes -->|"session auth"| APIKeyTable
```
## Implementation Guardrails
- All implementation in this initiative must follow [AGENTS.md](../../AGENTS.md).
- Do not design this as a brand-new standalone automation surface. Follow the existing patterns already used by the MCP implementation.
- For dashboard work, the API page must mirror the MCP structure:
- route shell under `apps/dashboard/src/routes/org/[slug]/automation/*`
- server-side data loading in `+page.server.ts`
- state and mutations in `apps/dashboard/src/lib/features/automation/api/automation.svelte.ts`
- request/response types in `apps/dashboard/src/lib/features/automation/utils/types.ts`
- page composition in `apps/dashboard/src/lib/features/automation/pages/api.svelte`
- Do not define new dashboard types inside `.svelte.ts` files. Keep feature types in `utils/types.ts` and infer them from the API client.
- Keep user-facing copy in `apps/dashboard/src/lib/utils/translations/en.json`.
## Current State Confirmed in Code
## 1. Database: `api_key` Table
### Org navigation and routes
Add a new table in [packages/db/src/schema.ts](packages/db/src/schema.ts):
These pieces already exist and should be treated as the current baseline:
```typescript
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),
});
```
- `apps/dashboard/src/lib/features/ui/navigation/org-navigation.ts`
- contains an admin-only `Automation` section under `/org/[slug]/automation`
- exposes nested entries for `MCP`, `Zapier`, and `API`
- `apps/dashboard/src/routes/org/[slug]/automation/+page.ts`
- redirects `/org/[slug]/automation` to `/org/[slug]/automation/mcp`
- `apps/dashboard/src/routes/org/[slug]/automation/mcp/+page.svelte`
- shipped MCP route shell using `Page.Root`, `Page.Header`, and `Page.Body`
- `apps/dashboard/src/routes/org/[slug]/automation/mcp/+page.server.ts`
- already loads automation keys and usage for `type=mcp`
- `apps/dashboard/src/routes/org/[slug]/automation/api/+page.svelte`
- already exists, but is currently a placeholder
- `apps/dashboard/src/routes/org/[slug]/automation/zapier/+page.svelte`
- already exists, but is currently a placeholder
- 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.
### Frontend automation feature foundation
## 2. API Key Queries
These pieces already exist and should be reused:
New file: [packages/db/src/queries/api-key/api-key.ts](packages/db/src/queries/api-key/api-key.ts)
- `apps/dashboard/src/lib/features/automation/api/automation.svelte.ts`
- shared automation API class for list/create/revoke/rotate
- `apps/dashboard/src/lib/features/automation/utils/types.ts`
- shared request and response inference types
- `apps/dashboard/src/lib/features/automation/pages/mcp.svelte`
- the current reference implementation for page structure and composition
- `apps/dashboard/src/lib/features/automation/pages/api.svelte`
- currently renders only a coming-soon callout
- `apps/dashboard/src/lib/utils/translations/en.json`
- already contains automation navigation and MCP copy
Functions:
### Backend automation-key foundation
- `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`
This work is already implemented and should not be re-invented as a separate `/api-key` system:
## 3. API Key Auth Middleware
- `packages/db/src/schema.ts`
- already defines `organization_api_key`
- already defines `organization_automation_usage`
- `packages/utils/src/validation/organization/automation-key.ts`
- already defines key types: `mcp`, `api`, `zapier`
- already defines scopes, query params, creation payload, and param validation
- `packages/db/src/queries/organization/automation-key.ts`
- already handles persistence for organization automation keys
- `apps/api/src/services/organization/automation-key.ts`
- already handles secret generation, hashing, creation, revoke, rotate, and auth
- `apps/api/src/routes/organization/automation.ts`
- already exposes:
- `GET /organization/automation/usage`
- `GET /organization/automation/keys`
- `POST /organization/automation/keys`
- `DELETE /organization/automation/keys/:keyId`
- `POST /organization/automation/keys/:keyId/rotate`
New file: [apps/api/src/middlewares/api-key.ts](apps/api/src/middlewares/api-key.ts)
### Important correction to the old PRD
- 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
The old PRD assumed:
## 4. Validation Schemas
- a brand-new Automation page still needed to be created
- Automation should be one page with internal tabs
- a standalone `/api-key` backend should be introduced
- `Webhooks` was one of the primary tabs
New file: [packages/utils/src/validation/api-key/api-key.ts](packages/utils/src/validation/api-key/api-key.ts)
That is no longer accurate.
Schemas for API key management and public API input validation:
The current product structure is:
- `ZCreateApiKey` -- `{ name?: string }`
- `ZRevokeApiKeyParam` -- `{ keyId: string (uuid) }`
- Public API schemas for course CRUD, audience CRUD, pagination, etc.
- org navigation owns the Automation section
- Automation is split into nested routes: `mcp`, `zapier`, and `api`
- the shared backend lives under `/organization/automation/*`
- MCP is the shipped reference page
- `api` and `zapier` are present as placeholder pages
## 5. Public API Routes (Versioned)
## Existing Foundation to Build On
### 1. Shared Automation Keys
The public API must reuse the existing organization-scoped automation-key model.
Current model:
- table: `organization_api_key`
- key types: `mcp`, `api`, `zapier`
- key prefixes: generated from the actual secret prefix
- auth: bearer token via the existing automation-key service/middleware path
- rotation and revocation: already implemented
- usage tracking: already implemented in `organization_automation_usage`
Design requirement:
- do not introduce a second key table or a second dashboard key-management surface
- do not reintroduce the older `/api-key` route design from the original PRD
- build the public API on the existing `organization/automation` contracts unless there is a deliberate migration plan
### 2. Dashboard Information Architecture
The Automation product is already organized as nested org routes, not a tabbed single-page shell.
Required route structure:
- `/org/[slug]/automation`
- redirect only
- `/org/[slug]/automation/mcp`
- existing reference implementation
- `/org/[slug]/automation/api`
- target page for public API setup and management
- `/org/[slug]/automation/zapier`
- separate placeholder or future implementation
Design requirement:
- keep the org sidebar/navigation pattern unchanged
- do not replace nested routes with a new internal tabs container for top-level Automation navigation
- if tabs are used inside a page, use them only for local subcontent, the way MCP uses setup tabs for client snippets
## Remaining Product Scope
### 3. API Page in Dashboard
The next dashboard milestone is to replace the placeholder API page with a real implementation.
### Target route files
- `apps/dashboard/src/routes/org/[slug]/automation/api/+page.svelte`
- `apps/dashboard/src/routes/org/[slug]/automation/api/+page.server.ts`
- `apps/dashboard/src/lib/features/automation/pages/api.svelte`
- `apps/dashboard/src/lib/features/automation/api/automation.svelte.ts`
- `apps/dashboard/src/lib/features/automation/utils/types.ts`
- `apps/dashboard/src/lib/utils/translations/en.json`
### Required structure
The API page must follow the same structural pattern already used by MCP:
1. Route shell:
- use `Page.Root`, `Page.Header`, `Page.HeaderContent`, `Page.Title`, and `Page.Body`
- set the page title from `automation.tabs.api`
- use an API-specific subtitle, not the generic coming-soon copy
2. Server load:
- add `+page.server.ts`
- load key data with `classroomio.organization.automation.keys.$get({ query: { type: 'api' } })`
- load usage data with `classroomio.organization.automation.usage.$get({ query: { type: 'api' } })`
- hydrate the shared `automationApi` store the same way MCP currently does
3. Feature page:
- keep composition in `apps/dashboard/src/lib/features/automation/pages/api.svelte`
- mirror the MCP page layout style and sectioning
- do not leave this page as a single callout
### Required API page sections
The real API page should include the following, using the MCP page as the UX baseline:
- admin permission callout for non-admin users
- usage cards or equivalent summary block for API-specific limits
- API key table filtered to `type === 'api'`
- create key flow
- rotate key flow
- revoke key flow
- one-time secret reveal after creation or rotation
- docs/discovery section with a link to the public API documentation
- API-specific setup guidance
### Reuse requirements
- reuse `automationApi` instead of introducing a separate API-only dashboard client
- reuse shared automation types in `utils/types.ts`
- add new helper utilities only if the API page needs API-specific setup snippets or docs helpers
### 4. Public API Routes
The actual public API is still pending.
### Router structure
New directory: `apps/api/src/routes/v1/`
Create:
Files:
- `apps/api/src/routes/v1/index.ts`
- `apps/api/src/routes/v1/courses.ts`
- `apps/api/src/routes/v1/audience.ts`
- [apps/api/src/routes/v1/courses.ts](apps/api/src/routes/v1/courses.ts) -- Course endpoints
- [apps/api/src/routes/v1/audience.ts](apps/api/src/routes/v1/audience.ts) -- Audience endpoints
- [apps/api/src/routes/v1/index.ts](apps/api/src/routes/v1/index.ts) -- Combines sub-routers
Register in [apps/api/src/app.ts](apps/api/src/app.ts):
Register in `apps/api/src/app.ts` as:
```typescript
.route('/v1', v1Router)
.route('/public-api/v1', v1Router)
```
All v1 routes use `apiKeyMiddleware` (not session auth).
### Authentication model
### Course Endpoints (`/v1/courses`)
All `/public-api/v1` routes must authenticate with organization automation keys.
Requirements:
| 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 |
- use the existing automation-key authentication path
- require `type: 'api'` or an equivalent scope model that intentionally permits public API access
- document and enforce scopes clearly
- do not rely on session auth for `/public-api/v1`
### Planned endpoint groups
### Audience Endpoints (`/v1/audience`)
Initial public API surface:
| Method | Path | Description |
| ------ | ---- | ----------- |
| GET | `/public-api/v1/courses` | List organization courses |
| POST | `/public-api/v1/courses` | Create a course |
| GET | `/public-api/v1/courses/:courseId` | Get course details |
| PUT | `/public-api/v1/courses/:courseId` | Update a course |
| DELETE | `/public-api/v1/courses/:courseId` | Soft-delete a course |
| GET | `/public-api/v1/courses/:courseId/export` | Export course content as JSON |
| GET | `/public-api/v1/courses/:courseId/students` | List enrolled students |
| GET | `/public-api/v1/audience` | List organization audience members |
| POST | `/public-api/v1/audience` | Add a student to the organization |
| POST | `/public-api/v1/audience/assign-courses` | Assign one or more audience members to one or more courses |
| GET | `/public-api/v1/audience/:studentId` | Get student details |
| PUT | `/public-api/v1/audience/:studentId` | Update student details |
| DELETE | `/public-api/v1/audience/:studentId` | Remove a student from the organization |
| 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 |
Enrollment decision:
- keep only the audience assignment endpoint for enrollment
- do not add a separate course-centric enrollment route such as `POST /public-api/v1/courses/:courseId/students`
- a single audience member plus a single course in `POST /public-api/v1/audience/assign-courses` covers the single-enrollment case
- multiple audience members and multiple courses in the same payload cover the bulk-assignment case
### API Key Management (session-authenticated, not public API)
### 5. Validation and Services
New file: [apps/api/src/routes/api-key/api-key.ts](apps/api/src/routes/api-key/api-key.ts)
Add public API schemas and services on top of the existing automation-key foundation.
Target files:
| 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 |
- `packages/utils/src/validation/...`
- `apps/api/src/services/v1/course.ts`
- `apps/api/src/services/v1/audience.ts`
Requirements:
Registered in `app.ts` as `.route('/api-key', apiKeyRouter)`. Protected by `authMiddleware` + `orgAdminMiddleware`.
- follow the route/service/validation split defined in `AGENTS.md`
- each route must return a single response type shape
- keep business logic in services, not routes
- reuse existing DB queries where possible
## 6. Services
### 6. OpenAPI and Docs
- [apps/api/src/services/api-key.ts](apps/api/src/services/api-key.ts) -- Key generation (crypto.randomBytes), hashing, CRUD logic
- [apps/api/src/services/v1/course.ts](apps/api/src/services/v1/course.ts) -- Public API course business logic (reuses existing queries from `@cio/db/queries/course`)
- [apps/api/src/services/v1/audience.ts](apps/api/src/services/v1/audience.ts) -- Public API audience business logic (reuses `getOrganizationAudience` and related queries)
The backend already has OpenAPI infrastructure. The remaining work is to ensure the new public API is included and discoverable.
## 7. Tests
### OpenAPI requirements
New test files under `apps/api/src/__tests__/`:
- all new `/public-api/v1/*` routes must be documented with the existing Hono OpenAPI patterns
- request params, query, body, success, and error responses must be declared
- avoid undocumented public API routes
- `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
### Spec publishing
Tests will use Hono's `app.request()` test helper (no HTTP server needed). Mock the DB layer with `vi.mock()`.
Use the existing script:
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.
- `apps/api/scripts/upload-openapi-spec.ts`
## 8. Dashboard UI: Automation Page
Required flow after API changes:
### Sidebar Navigation
1. Implement or update routes and schemas.
2. Run `pnpm --filter @cio/api upload:openapi`.
3. Verify the latest spec includes the new routes.
Add an "Automation" nav item in [apps/dashboard/src/lib/features/ui/navigation/org-navigation.ts](apps/dashboard/src/lib/features/ui/navigation/org-navigation.ts), placed before Settings, with `requiresAdmin: true`:
### Docs integration
```typescript
{
titleKey: 'org_navigation.automation',
path: '/automation',
icon: AutomationIcon, // use Zap or Workflow icon from lucide
requiresAdmin: true,
matchPattern: '^/org/[^/]+/automation(/.*)?$'
}
```
Add the public API docs experience in `apps/docs`.
### Route
Target tasks:
New file: [apps/dashboard/src/routes/org/[slug]/automation/+page.svelte](apps/dashboard/src/routes/org/[slug]/automation/+page.svelte)
1. Add the API docs page or route.
2. Wire it to the published OpenAPI source.
3. Add docs navigation for API.
4. Verify the docs build still passes.
### Page Structure
### 7. Testing
```
+----------------------------------------------+
| Automation |
| Connect ClassroomIO with your tools and |
| workflows. |
+----------------------------------------------+
| [ API ] [ Zapier ] [ Webhooks ] |
+----------------------------------------------+
| |
| (Tab content based on selection) |
| |
+----------------------------------------------+
```
Add tests for:
- Title: "Automation" with description text
- Uses `Tabs` from `@cio/ui/base/tabs`
- Three tabs: **API**, **Zapier**, **Webhooks**
- organization automation key auth behavior relevant to public API access
- `/v1/courses`
- `/v1/audience`
- OpenAPI contract inclusion for public API routes
### API Tab Content
Use Hono app-level testing where possible.
**Free plan users:** Show `UpgradeBanner` component (same pattern as other pages). Disable all controls.
### 8. Build and Verification
**Paid plan users:**
Required verification commands after implementation:
- 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](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](apps/dashboard/src/lib/features/automation/utils/types.ts)
### Translations
Add keys to [apps/dashboard/src/lib/utils/translations/en.json](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.
Manual verification:
1. `/org/[slug]/automation` still redirects to `/org/[slug]/automation/mcp`
2. `/org/[slug]/automation/api` now renders the real API page rather than the placeholder
3. API key creation, rotation, and revoke flows work for `type=api`
4. `/public-api/v1/*` routes respond under the new base path
5. docs navigation exposes the public API docs entry