Skip to content

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:

SearchHit = Annotated[
    Union[TicketHit, WikiPageHit],
    Field(discriminator="type"),
]

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:

cd frontend
pnpm gen:types

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.ts is regenerated by pnpm gen:types against a running backend. If a backend change is not followed by a regen, api.ts lags 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#