API and MCP#
Ticket Board exposes its domain through two transports: a REST API for humans and bots, and a Model Context Protocol (MCP) server for bots driven by language models. Both transports talk to the same service layer, so any rule enforced for one is enforced for the other.
This section is the operator-and-integrator reference for both surfaces. For the conceptual "why" behind a single service layer with two transports, see the architecture overview.
The two surfaces at a glance#
| Surface | Base path | Auth | Audience | Wire format |
|---|---|---|---|---|
| REST | /api/v1 |
Session cookie (humans) or X-API-Key header (bots) |
Browser UI, bot SDKs, curl, anything that speaks HTTP/JSON | JSON over HTTP |
| MCP | /mcp/bot/{token} |
Token embedded in the URL path | LLM agents speaking the Model Context Protocol | JSON-RPC over HTTP |
A bot has exactly one token. That token authenticates it on both surfaces — as X-API-Key over REST and as the path segment over MCP. There is no separate identity for the two transports; one bot, one credential, one audit identity.
Which surface do I use?#
- Building a web client or an integration script. Use REST. The OpenAPI document is the contract; auto-generated TypeScript and Python clients are the easiest path.
- Wiring up an LLM agent (Claude Code, a custom MCP client, etc.). Use MCP. You get a curated tool catalog that the model already knows how to introspect via
tools/list. - Bots that do both. Common. The bot reads tickets through MCP tool calls and uses REST for the occasional batch import where idempotency keys matter. The same token works for both.
Shared semantics#
Everything in the list below holds across both transports because it lives in the service layer, not in the routers.
- Authorization. A bot is bound to a project allowlist and a read/write mode. Write attempts outside scope return
auth.forbidden; write attempts by a read-only bot returnauth.read_only_bot. - Workspace scope. All requests resolve to a single workspace (v1 has exactly one). Workspace ids never appear in URLs or tool arguments.
- Identifiers. Resources are addressed by UUIDv7. Tickets also accept their human-readable display id (
PROJ-123) anywhere a single ticket is addressed. - Errors. A single envelope shape, machine-readable
code, human-readablemessage, optionaldetails. See REST error envelope and the MCP error mapping table. - Pagination. Opaque cursor pagination with the same
data/page.next_cursor/page.has_moreenvelope on both surfaces. Cursors are portable: a cursor handed out over REST can be passed back through an MCP tool call. - Rate limiting. Login is rate-limited per-IP and per-username today; per-actor REST/MCP limits are planned (the
user.rate_limit_per_minutecolumn is reserved). See Rate limiting. - Audit. Every write produces an
audit_eventattributed to the calling actor. MCP tool calls additionally produce atool_invocation_logrow carrying the redacted arguments and outcome.
OpenAPI#
The REST surface is described by an OpenAPI 3 document FastAPI generates at runtime. The frontend regenerates its TypeScript types from it on every API change. See OpenAPI spec for where it lives at runtime, how to point a code generator at it, and the conventions the schema follows (operation ids, discriminated unions, etc.).
Reference material#
- REST API — auth, error envelope, pagination, idempotency, ETags, and the full endpoint catalog by resource group.
- MCP server — per-bot mount, the 29-tool catalog, MCP resources, and the tool-invocation log.
- OpenAPI spec — where the schema is served, code generation tips, and stability guarantees.
Cross-references#
- Bots — how to create bots, scope them, and rotate their tokens from the UI.
- Audit log — how to read what bots did, including the per-call tool invocation log.
- Auth and identity — the full identity model: session cookies, API keys, MCP token resolution, and rate limiting.
- ADR-0002: Per-bot MCP URL with token-in-path.
- ADR-0004: REST over GraphQL.