API and MCP
Norn has one HTTP API. The dashboard is a client of it, and so is everything else — there is no private surface the browser gets and integrations do not.
The contract is an OpenAPI 3.0.3 document, api/http/v1/dashboard.yaml, and it is the single source
of truth. The Go server and the TypeScript client are both generated from it, so the spec cannot
drift from either.
Base URL
https://norn.example.com/v1
Every path below is relative to that. On the managed cloud the origin is your workspace host; when
self-hosting it is whatever you set as ORIGIN, since the API and the dashboard
share one origin.
Authentication
Two schemes, either of which satisfies a request:
| Scheme | Transport | Used by |
|---|---|---|
sessionCookie | Cookie norn_session | The dashboard, after sign-in |
bearerToken | Authorization: Bearer … | Personal and workspace API tokens |
curl https://norn.example.com/v1/accounts/me \
-H "Authorization: Bearer $NORN_TOKEN"
The session cookie is HttpOnly and host-only. Frontend code never reads it, and a password hash
never leaves the backend.
Errors
Failures are RFC 7807 problem documents, not ad-hoc JSON:
{
"type": "https://norn.so/problems/validation",
"title": "Validation failed",
"status": 422,
"detail": "title must not be empty"
}
Branch on status and type. Do not parse detail — it is human-facing copy.
Resource groups
The spec is organised by tag:
| Group | Covers |
|---|---|
health, instance | Liveness, and what an instance offers before anyone signs in |
auth | Sign-up, sign-in, password reset, logout |
accounts, sessions | The signed-in account and its active sessions |
workspaces, teams, invitations | Organisation structure and membership |
issues, states, triage, saved-views | The tracker itself |
tokens, connections, webhooks | Integration surfaces |
agents | Agent connections and their authorisations |
sso | OIDC and SAML sign-in |
notifications, search, audit | Cross-cutting reads |
Endpoints the browser calls directly
Most page data is loaded server-side. A short, closed list of endpoints is called straight from the browser because it needs something a server load cannot give it:
GET /v1/workspaces/{workspaceId}/events— a Server-Sent Events stream. Proxies must not buffer it, and must not time it out.- Presigned attachment uploads and downloads — they need upload progress and a file dialog.
- The streamed audit export.
- Third-party-initiated SSO, SAML,
/oauth,/.well-known, and/mcppaths.
MCP
Agents connect over the Model Context Protocol. The connection lifecycle is explicit and auditable — an agent does not get ambient access to a workspace.
| Path | Purpose |
|---|---|
GET /v1/mcp/connections | List the caller's MCP connections |
DELETE /v1/mcp/connections/{connectionId} | Revoke one |
GET /v1/mcp/authorizations/{requestId} | Read a pending authorisation request |
POST /v1/mcp/authorizations/{requestId}/approve | Approve it |
POST /v1/mcp/authorizations/{requestId}/deny | Deny it |
GET /v1/workspaces/{workspaceId}/mcp-connections | Administer connections for a workspace |
An authorisation request is raised when an agent first asks for access. Until somebody approves it, the agent has none.
Generated clients beat hand-written ones. Point openapi-typescript, oapi-codegen, or your
language's equivalent at api/http/v1/dashboard.yaml and regenerate whenever the spec changes.