OpenAPI spec#
FastAPI auto-generates an OpenAPI 3 document describing the REST surface. It is the wire contract: the frontend regenerates its TypeScript types from it on every API change, and bot SDKs are expected to do the same.
Where it lives at runtime#
| Path | Purpose | Notes |
|---|---|---|
/api/v1/openapi.json |
The OpenAPI 3 document. | Always served; the source of truth for code generators. |
/api/v1/docs |
Swagger UI. | Disabled in production via the DOCS_ENABLED setting (see .env.example). |
/api/v1/redoc |
ReDoc renderer. | Disabled in production via the same setting. |
In development:
curl -s http://localhost/api/v1/openapi.json | jq '.info.version'
# → "0.1.0"
open http://localhost/api/v1/docs
The JSON document is always served. The interactive renderers (/api/v1/docs, /api/v1/redoc) are gated by DOCS_ENABLED.
DOCS_ENABLED is off by default
The shipped .env.example sets DOCS_ENABLED=false and the Pydantic default is also False. Flip to true only on development hosts; the interactive renderers reveal route names and request shapes that bot operators would otherwise not see.
Conventions the schema follows#
The document is generated, not hand-maintained. The generator is configured to keep it stable and code-generator-friendly.
operation_id per route#
Every route declares an explicit operation_id matching <resource>.<verb> or <resource>.<sub>.<verb>:
| Route | operation_id |
|---|---|
POST /auth/login |
auth.login |
POST /auth/change-password |
auth.change_password |
GET /tickets |
tickets.list |
POST /projects/{id}/tickets |
tickets.create |
POST /tickets/{id_or_display_id}/transition |
tickets.transition |
POST /bots/{id}/tokens/rotate |
bots.tokens.rotate |
POST /projects/{id}/workflow-states/reorder |
projects.workflow_states.reorder |
GET /audit/tools |
audit.tools.list |
This drives method names in generated TypeScript and Python clients. Without an explicit id FastAPI assembles a noisy default from function name + tag.
Tag grouping#
Every router file has exactly one APIRouter(prefix="/...", tags=["..."]). The result is one tag per resource group: auth, users, bots, projects, tickets, comments, wiki, sticky-notes, search, audit, webhooks. The generated Swagger UI groups endpoints by these tags.
Discriminated unions#
Discriminated unions are declared with Pydantic v2's discriminator argument:
This produces a proper OpenAPI discriminator object in the schema. TypeScript code generators (openapi-typescript) then emit a usable discriminated union with narrowing on result.type.
Unions affected: User (human | bot), SearchHit (ticket | wiki_page), AuditEventPayload (per event_type).
Versioning the document#
info.version tracks ticket_board.__version__ (0.1.0 at the time of writing) and is incremented in step with the package.
- Minor bump for additive changes (new endpoint, new optional field, new enum value for an open enum).
- Major bump would require
/api/v2. There is no v2 in scope. - The package version is the single source of truth — there is no separate "API version" string to keep in sync.
Generating clients#
Frontend (TypeScript)#
The frontend uses openapi-typescript and openapi-fetch. The generated types land in frontend/src/types/api.ts. Regeneration is wired to a script:
This script fetches openapi.json from a running backend and emits the types. Run it after any backend change that touches a schema; CI does not yet enforce that api.ts matches the served schema.
Bot SDKs#
There is no official ticket-board SDK in v1; the OpenAPI document is the contract. Any code generator that accepts OpenAPI 3 produces a usable client.
A common path for Python:
# In a bot project:
python -m pip install openapi-python-client
openapi-python-client generate \
--url http://localhost/api/v1/openapi.json
The generated client honors the operation_id naming, so methods read like client.tickets.create(...).
For Python bots that prefer to skip code generation, a thin wrapper around httpx plus the REST endpoint catalog on this site is usually enough.
Stability guarantees#
| Change | Triggers a version bump? | Triggers /api/v2? |
|---|---|---|
| New endpoint | Minor bump | No |
| New optional request field | Minor bump | No |
| New enum value for an open enum | Minor bump (clients must tolerate unknown values per the spec) | No |
| New required response field | Minor bump | No |
| Removing an endpoint | Not allowed in v1; would be called out in release notes and any v2 cutover | Yes |
| Removing or renaming a response field | Not allowed | Yes |
| Changing a field's type | Not allowed | Yes |
Renaming an operation_id |
Not allowed; treated as a breaking change for code generators | Yes |
| Changing error envelope structure | Not allowed | Yes |
In practical terms: the contract is conservative. Additive changes are routine; subtractive changes are gated behind a major version.
Common questions#
Can I cache the schema?#
Yes. The OpenAPI document is generated at process startup and served as a static JSON blob; it changes only when the backend ships. Caching by ETag works as expected.
Where does the schema live in development?#
Two notes on freshness:
- The frontend's checked-in
api.tsis regenerated bypnpm gen:typesagainst a running backend. If a backend change is not followed by a regen,api.tslags the served schema until you re-run the script. - Swagger UI and ReDoc cache the JSON in the browser. A hard refresh or an incognito window will pick up the freshly served document.
Where do the per-endpoint examples come from?#
FastAPI generates examples from the Pydantic models' examples config. The most useful examples live on the request bodies for tickets.create, bots.create, and webhooks.create — the three creates whose shape is least obvious.
Does the MCP server have an OpenAPI document?#
No. MCP is JSON-RPC over HTTP, not REST, and its discovery mechanism is tools/list. See MCP tool discovery. The MCP catalog and the OpenAPI document describe two different surfaces; the underlying domain is the same.
Cross-references#
- REST API — the surface this document describes.
- MCP server — the second transport, discoverable via
tools/list. - Environment variables —
DOCS_ENABLEDand related settings. - ADR-0004: REST over GraphQL — why OpenAPI and not a query language.