Api Reference

How We Document APIs

Edit this page

How We Document APIs

This document explains our code-first approach to API documentation. We maintain a single source of truth (the API manifest) and auto-generate Mintlify reference tables from it.

Overview

Our API documentation workflow follows these principles:

  1. Code-first: The manifest in packages/api-spec/src/manifest.ts is the source of truth
  2. Auto-generated: Reference tables are generated from the manifest
  3. Scope-safe: Guardrails keep non–tenant-facing routes out of public reference material
  4. Lightweight: Regex-based scanning, no runtime code changes

Workflow

Step 1: Scan Routes

Discover all API routes in the codebase:

1pnpm api:scan

This scans apps/scm/app/api/**/route.ts files and generates:

  • packages/.generated/api-routes.json - Complete route inventory
  • packages/.generated/manifest-stubs.ts - Suggested manifest entries

What it does:

  • Finds all route handlers
  • Detects HTTP methods (GET, POST, etc.)
  • Identifies authentication patterns
  • Extracts Zod schema names
  • Classifies routes as tenant-facing vs internal-only

Step 2: Generate Stubs

Create manifest stub suggestions:

1pnpm api:stubs

This reads the scan results and generates:

  • packages/.generated/api-manifest.stubs.ts - Ready-to-copy manifest entries

What it does:

  • Converts paths to stable IDs
  • Infers tags from path prefixes
  • Detects auth types
  • Sets default stability (beta for safety)
  • Marks internal-only routes

Step 3: Refine Manifest

Copy relevant stubs to packages/api-spec/src/manifest.ts and:

  1. Fill in summaries: Replace "TODO: Add summary" with clear descriptions
  2. Add descriptions: Provide detailed endpoint documentation
  3. Add examples: Include request/response examples
  4. Set permissions: Document required permissions
  5. Adjust stability: Mark stable endpoints as stable (default is beta)
  6. Add query params: Document query parameters
  7. Add path params: Document dynamic path segments

Example:

1{
2 id: 'forecast.events.create',
3 method: 'POST',
4 path: '/api/forecast/events',
5 summary: 'Create an external event',
6 description: 'Create a new external event (promotion, holiday, etc.) to drive forecast adjustments',
7 tags: ['forecasting'],
8 audience: 'tenant',
9 auth: 'session',
10 stability: 'stable',
11 permissions: ['forecast.write'],
12 requestSchema: 'CreateEventSchema',
13 queryParams: [
14 { name: 'from', type: 'string', required: false, description: 'Start date (ISO string)' },
15 { name: 'to', type: 'string', required: false, description: 'End date (ISO string)' },
16 ],
17 examples: [
18 {
19 title: 'Create promotion event',
20 request: {
21 body: {
22 name: 'Summer Sale 2024',
23 type: 'PROMOTION',
24 startsAt: '2024-06-01T00:00:00Z',
25 endsAt: '2024-06-30T23:59:59Z',
26 },
27 },
28 response: {
29 status: 201,
30 body: {
31 id: 'event_789',
32 name: 'Summer Sale 2024',
33 },
34 },
35 },
36 ],
37}

Step 4: Validate

Check the manifest against guardrails:

1pnpm api:validate

Validations:

  • ✅ No internal-only or non–workspace-scoped routes in the public manifest
  • ✅ All tenant routes have proper auth
  • ✅ All IDs are unique
  • ✅ All paths start with /api/
  • ✅ All routes have summaries and tags

Step 5: Generate Docs

Update Mintlify reference tables:

1pnpm api:docs

This updates the delimited blocks in:

  • docs-site/api-reference/forecasting.mdx
  • docs-site/api-reference/inventory.mdx
  • docs-site/api-reference/alerts.mdx
  • docs-site/api-reference/transfers.mdx
  • docs-site/api-reference/catalog.mdx

What it does:

  • Groups routes by tag
  • Sorts by path, then method
  • Generates markdown tables
  • Includes permissions when available
  • Marks beta endpoints with ⚠️
  • Only updates delimited blocks (preserves other content)

CI Guard

Before committing, run the CI guard:

1pnpm api:guard

Checks:

  • ✅ Manifest doesn't contain excluded internal-only paths
  • ✅ Generated docs blocks are present in all target files
  • ✅ Docs blocks are properly formatted

This should be run in CI to prevent regressions.

Scope Rules

✅ Include

  • Tenant-facing APIs in apps/scm/app/api/**
  • APIs consumed by external integrators
  • Tenant-admin configuration APIs (/api/admin/*)

❌ Exclude

  • Non–workspace-scoped platform operations
  • Workspace onboarding or lifecycle tools operated by Better Data staff
  • Internal scheduled jobs and maintenance surfaces (unless explicitly tenant-configurable and approved for public docs)

⚠️ When in Doubt

Mark ambiguous endpoints as internalOnly: true until confirmed tenant-facing.

File Structure

1packages/api-spec/
2├── src/
3│ ├── types.ts # Type definitions
4│ ├── manifest.ts # API route specifications (source of truth)
5│ └── index.ts # Package exports
6├── scripts/
7│ ├── scan-routes.ts # Route scanner
8│ ├── build-manifest.ts # Manifest stub generator
9│ ├── validate-manifest.ts # Manifest validator
10│ ├── generate-mintlify.ts # Docs generator
11│ └── ci-guard.ts # CI guard
12└── package.json
13 
14packages/.generated/
15├── api-routes.json # Scan results
16├── manifest-stubs.ts # Generated stubs
17└── api-manifest.stubs.ts # Refined stubs
18 
19docs-site/api-reference/
20├── forecasting.mdx # Contains generated table block
21├── inventory.mdx # Contains generated table block
22├── alerts.mdx # Contains generated table block
23├── transfers.mdx # Contains generated table block
24└── catalog.mdx # Contains generated table block

Delimited Blocks

Generated tables are wrapped in delimited blocks:

1{/* BEGIN:API_TABLE tag=forecasting */}
2{/* this block is generated; do not edit by hand */}
3| Method | Path | Summary | Auth | Stability | Permissions |
4| ... | ... | ... | ... | ... | ... |
5{/* END:API_TABLE tag=forecasting */}

Important:

  • Do NOT edit content between these markers
  • The generator will overwrite it
  • All other content in the file is preserved

Best Practices

  1. Keep it lightweight: Use regex-based scanning, don't refactor runtime code
  2. Prefer correctness: Mark ambiguous endpoints as internalOnly: true
  3. Scope safety first: Prefer excluding ambiguous routes from the public manifest
  4. Update regularly: Run api:scan when adding new routes
  5. Validate before commit: Run api:validate and api:guard

Troubleshooting

"Route not in docs"

  1. Check if route is in manifest: packages/api-spec/src/manifest.ts
  2. Verify route has correct tags matching target file
  3. Run pnpm api:docs to regenerate tables
  4. Check if route is marked internalOnly: true

"CI guard failing"

  1. Run pnpm api:validate and review manifest exclusions
  2. Verify docs blocks exist: Check target files for delimited blocks
  3. Regenerate docs: pnpm api:docs

"Missing permissions column"

Permissions column is only shown when routes have permissions defined in manifest. Add permissions to routes that need them.

OpenAPI publication guardrails (docs site)

The docs repository ships public/api-reference/openapi.json and runs node scripts/validate-public-openapi.mjs as part of pnpm run docs:guardrails (prebuild). Policy and allowlist live in:

  • internal/docs/openapi-publication-governance.md
  • scripts/openapi-publication-config.json

When replacing the public bundle, update the tag allowlist intentionally; do not rely on blacklist-only reviews.