Skip to main content

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:

SchemeTransportUsed by
sessionCookieCookie norn_sessionThe dashboard, after sign-in
bearerTokenAuthorization: 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:

GroupCovers
health, instanceLiveness, and what an instance offers before anyone signs in
authSign-up, sign-in, password reset, logout
accounts, sessionsThe signed-in account and its active sessions
workspaces, teams, invitationsOrganisation structure and membership
issues, states, triage, saved-viewsThe tracker itself
tokens, connections, webhooksIntegration surfaces
agentsAgent connections and their authorisations
ssoOIDC and SAML sign-in
notifications, search, auditCross-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 /mcp paths.

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.

PathPurpose
GET /v1/mcp/connectionsList 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}/approveApprove it
POST /v1/mcp/authorizations/{requestId}/denyDeny it
GET /v1/workspaces/{workspaceId}/mcp-connectionsAdminister connections for a workspace

An authorisation request is raised when an agent first asks for access. Until somebody approves it, the agent has none.

tip

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.