# API Contract Stub This project is **REST + OpenAPI**. The backend serves the live spec via springdoc at `http://localhost:8080/v3/api-docs` (dev profile only), and the frontend generates its TypeScript client from it with `npm run generate:api` (`openapi-typescript` → `frontend/src/lib/generated/api.ts`). There is no GraphQL in this stack. > **The live spec is generated from the Java controllers — it is the source of truth.** A > hand-written contract under `.specify/features//api-contract.yaml` is a *design > artifact*: it pins the intended shape during spec review and is checked against the > generated spec once the endpoint exists. Keep it OpenAPI **3.1**, and keep > `@Schema(requiredMode = REQUIRED)` on the Java side as the real driver of `required`. ## How to use this stub 1. Copy the skeleton below to `.specify/features//api-contract.yaml`. 2. Fill in the paths/methods/schemas your feature adds. Every mutating path documents the `403`/`401` responses and the `cookieAuth` security requirement (matching the real `@RequirePermission` gate). 3. The `sdd-gate.yml` CI job lints any changed `api-contract.yaml` with Spectral (`npx @stoplight/spectral-cli lint`). Run it locally the same way before pushing. 4. After the endpoint ships, run `npm run generate:api` and diff the generated types against this contract; reconcile any drift (the generated spec wins — update the contract). ## OpenAPI 3.1 skeleton ```yaml openapi: 3.1.0 info: title: Familienarchiv API — version: 0.0.1-SNAPSHOT description: Design-time contract for . Source of truth is the generated /v3/api-docs. servers: - url: http://localhost:8080 description: Local backend (dev profile) - url: https://archiv.raddatz.cloud description: Production (behind Caddy) components: securitySchemes: cookieAuth: # Spring Session JDBC — opaque session id in the SESSION cookie type: apiKey in: cookie name: SESSION schemas: ErrorResponse: # shape produced by GlobalExceptionHandler type: object required: [code, message] properties: code: type: string description: Machine-readable ErrorCode (see ErrorCode.java / errors.ts). example: FORBIDDEN message: type: string # : # always a view, never a lazy-collection entity (ADR-036) # type: object # required: [id] # properties: # id: { type: string, format: uuid } security: - cookieAuth: [] # default: every path requires a session unless overridden to [] paths: /api/: post: summary: operationId: security: - cookieAuth: [] # plus @RequirePermission(Permission.X) on the controller requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/' } responses: '201': description: Created content: application/json: schema: { $ref: '#/components/schemas/' } '400': { description: Validation failed, content: { application/json: { schema: { $ref: '#/components/schemas/ErrorResponse' } } } } '401': { description: Unauthenticated, content: { application/json: { schema: { $ref: '#/components/schemas/ErrorResponse' } } } } '403': { description: Missing permission, content: { application/json: { schema: { $ref: '#/components/schemas/ErrorResponse' } } } } ``` ## Validating the contract in CI The `sdd-gate.yml` workflow runs, on PRs that touch a `api-contract.yaml`: ```bash npx @stoplight/spectral-cli lint .specify/features/**/api-contract.yaml ``` Spectral's default OpenAPI ruleset catches malformed specs, missing `operationId`s, and undefined `$ref`s. Add a `.spectral.yaml` at the repo root to tune rules if needed.