Add API Endpoint

OpenAPI-first. The spec at openapi/v1.yaml is the single source of truth — types, scopes, schemas, and CLI surfaces all derive from it.

Default stance

Edit openapi/v1.yaml first, then implement. Implementing first and back-filling the spec is how endpoints drift from documentation, scopes get inconsistent, and the SDK breaks. The spec is also what verify-openapi-routes.py and verify-scope-sync.py enforce — go out of order and the gates will block your PR.

Route handlers should be thin. Auth, scope checks, and request shape live in withApiKeyAuth. Business logic lives in lib/<resource>.ts (queries) or lib/actions/<resource>.ts (mutations). If your route handler has more than ~30 lines of logic, you're probably putting business logic in the wrong layer.

Use this skill when

Adding or changing public API routes under /api/v1, adding a new scope, restructuring auth/permissions on an existing endpoint, or designing the API surface for a new resource.

New endpoint checklist

  1. Define in OpenAPI spec — add path, request/response schemas, and x-required-scopes in openapi/v1.yaml.
  2. Create route handlerapp/api/v1/<resource>/route.ts.
  3. Auth — wrap in withApiKeyAuth from lib/api-v1-auth.ts.
  4. Scopes — pass requiredScopes to the wrapper; hasScopes() from lib/scopes.ts does the work.
  5. Business logic — queries in lib/<resource>.ts, mutations in lib/actions/<resource>.ts.
  6. Database — if new tables, edit lib/schema.ts and run bunx drizzle-kit generate (see db-health).
  7. Teststests/unit/.
  8. CLI commandcli/cmd/<resource>.go (see cli-development).
  9. Webhook events — emit any meaningful state change with sendWebhookEventAsync (see webhooks-and-events).
  10. Gates./scripts/gates.sh.
  11. SDK — if spec changed, regenerate via bun run generate:sdk and follow release-sdk for publication.

Route handler pattern

import { NextRequest, NextResponse } from "next/server"
import { withApiKeyAuth } from "@/lib/api-v1-auth"

export const GET = withApiKeyAuth(
  async (_request: NextRequest, { apiKey, user, organizationId }) => {
    const result = await listResources(organizationId)
    return NextResponse.json({ data: result })
  },
  { requiredScopes: ["resource:read"] }
)

The wrapper validates the API key, resolves user + organization from it, enforces scopes, and rejects with the right status codes if any check fails. Don't reimplement any of that in the handler.

Adding a new scope

  1. Add to lib/scopes.ts in ALL_SCOPES and SCOPE_DESCRIPTIONS.
  2. Add to openapi/v1.yaml under x-all-scopes and components.schemas.Scope.enum.
  3. Add x-required-scopes to the endpoints that need it.
  4. Enforce via requiredScopes in withApiKeyAuth.
  5. Tests + gates.

verify-scope-sync.py will fail the build if these get out of sync.

Architecture layers

openapi/v1.yaml          ← spec (source of truth)
app/api/v1/*/route.ts    ← thin route handler (auth + dispatch)
lib/api-v1-auth.ts       ← withApiKeyAuth wrapper
lib/scopes.ts            ← scope definitions
lib/<resource>.ts         ← queries
lib/actions/<resource>.ts ← mutations
tests/unit/              ← tests
cli/cmd/<resource>.go    ← CLI command

Hard rules

  • Don't edit sdks/typescript/src/types.ts manually. It's generated.
  • Don't add scopes only in code or only in spec — they must match. The verifier will fail your PR.
  • Don't write fat route handlers. Push logic into lib/.
  • Don't introduce a new auth path. All v1 endpoints go through withApiKeyAuth. If you need session auth, that's a non-v1 endpoint and lives elsewhere (/api/webhooks/portal/route.ts is one example).

Current scopes

ScopeGrants
keys:read / keys:writeAPI keys
usage:read / usage:writeUsage metrics
webhooks:read / webhooks:writeEvents list, SSE, test events, portal

Where things live

FilePurpose
openapi/v1.yamlSpec — edit this first
app/api/v1/Route handlers
lib/api-v1-auth.tswithApiKeyAuth
lib/scopes.tsScope definitions
lib/api-keys.tsKey validation, scope checks
scripts/verify-openapi-routes.pyGate: every spec path has a handler
scripts/verify-scope-sync.pyGate: scopes match across spec + code
openapi/AGENTS.mdSpec editing rules
docs/api.mdFull endpoint reference

Auxiliary content