Skip to content

REST API

All endpoints require Authorization: Bearer <token> unless noted. Responses are JSON. Endpoints under /auth/* and /oauth/* live on the klaxon-auth binary (port 3001 by default); everything else lives on klaxon-server (port 3000). In production the Ingress path-routes them to the right pod — from a client's perspective they share one origin.

Every write endpoint under /api/admin/* requires a specific permission string (see Roles & Permissions). A missing permission returns 403 with {"error":"permission denied","required":"..."}.

Health & Ops

MethodPathAuthDescription
GET/healthNoReturns "ok"
GET/readyNoChecks DB connection (503 if down)
GET/metricsNoPrometheus metrics

Auth

The auth binary exposes three classes of endpoint: IdP login flows, OAuth 2.1 Authorization Server, and the account-management surface the UI calls once a session exists.

IdP login

MethodPathAuthDescription
GET/auth/oauth/githubNoStart GitHub OAuth flow (redirects)
GET/auth/oauth/github/callbackNoGitHub OAuth callback
GET/auth/oauth/googleNoStart Google OAuth flow (redirects)
GET/auth/oauth/google/callbackNoGoogle OAuth callback
GET/auth/oauth/appleNoStart Sign in with Apple flow (redirects)
POST/auth/oauth/apple/callbackNoApple OAuth callback (POST form per Apple spec)
POST/auth/magic-linkNoRequest magic link login email
GET/auth/verifyNoExchange magic link token for session

See Authentication for the full IdP setup + env vars for each provider.

OAuth 2.1 Authorization Server

MethodPathAuthDescription
GET/.well-known/oauth-authorization-serverNoRFC 8414 metadata discovery
GET/.well-known/oauth-protected-resourceNoRFC 9728 resource metadata
POST/oauth/registerNoRFC 7591 dynamic client registration
GET/oauth/authorizeSessionBrowser authorization endpoint + consent
POST/oauth/tokenNoToken exchange (PKCE S256 required) + refresh
POST/oauth/revokeNoRFC 7009 token revocation

Account & sessions

MethodPathAuthDescription
POST/auth/logoutYesRevoke the current session
GET/auth/meYesReturn authenticated identity
POST/auth/orgsYesCreate a new organization
POST/auth/switch-orgYesMint a new session scoped to a different org the user belongs to
GET/auth/api-keysYesList this user's API keys
POST/auth/api-keysYesCreate API key (auto-creates agent)
POST/auth/api-keys/revokeYesSoft-revoke an API key
bash
curl -X POST http://localhost:3000/auth/magic-link \
  -H "Content-Type: application/json" \
  -d '{"email": "alice@example.com", "org_id": "ORG_UUID"}'

Response (always 200 — doesn't leak user existence):

json
{ "message": "If an account exists, a login link has been sent." }

In dev mode (no SMTP configured), the response includes the token:

json
{ "message": "Check your email (dev: token in response)", "token": "base64url-token" }
bash
curl "http://localhost:3000/auth/verify?token=base64url-token-from-email"

Response:

json
{ "token": "session-bearer-token", "user_id": "...", "org_id": "..." }

Items

MethodPathAuthDescription
POST/api/itemsYesCreate item
GET/api/itemsYesList items (cursor-paginated)
GET/api/items/:idYesGet item by ID
PATCH/api/items/:idYesUpdate item fields
POST/api/items/:id/answerYesSubmit form response (validates against schema)
POST/api/items/:id/ackYesMark as viewed
POST/api/items/:id/dismissYesDismiss
POST/api/items/:id/archiveYesArchive
POST/api/items/:id/unarchiveYesUnarchive
POST/api/items/:id/restoreYesRestore dismissed/expired/withdrawn
POST/api/items/:id/snoozeYesSnooze until datetime
POST/api/items/:id/unsnoozeYesRemove snooze
POST/api/items/:id/pinYesPin to top
POST/api/items/:id/unpinYesUnpin

List Items — Query Parameters

ParamTypeDefaultDescription
limitint50Max items (capped at 200)
cursorstringPagination cursor from previous response
statusstringFilter by status (open, answered, etc.)
channel_idUUIDFilter by channel
include_archivedboolfalseInclude archived items
qstringFull-text search on title + message

List Response

json
{
  "items": [...],
  "next_cursor": "base64-encoded-cursor-or-null"
}

Create Item — Body

json
{
  "title": "Deploy failed",
  "message": "Error in stage 3",
  "level": "error",
  "priority": 3,
  "channel_id": "uuid",
  "tags": ["deploy", "prod"],
  "form": { ... },
  "metadata": { "commit": "abc123" },
  "callback_url": "https://n8n.example.com/webhook/abc123",
  "deadline_at": "2026-04-15T12:00:00Z"
}

Only title is required. The callback_url causes the server to POST the response back to that URL when the item is answered/dismissed/acked (see Integrations).

Answer Item — Body

json
{ "values": { "field_id": "value", ... } }

Returns 422 with field-level errors if validation fails:

json
{ "error": "validation failed", "fields": { "name": "field 'name' is required" } }

Timeline

MethodPathAuthDescription
GET/api/items/:id/timelineYesMerged audit events + comments (chronological)

Returns a unified feed:

json
[
  { "entry_type": "event", "action": "item.create", "actor_type": "agent", "created_at": "..." },
  { "entry_type": "comment", "body": "investigating", "actor_type": "user", "created_at": "..." }
]

Comments

MethodPathAuthDescription
GET/api/items/:id/commentsYesList comments (chronological)
POST/api/items/:id/commentsYesAdd comment

Add Comment — Body

json
{ "body": "Looks good to me" }

Attachments

MethodPathAuthDescription
GET/api/items/:id/attachmentsYesList attachment metadata
POST/api/items/:id/attachmentsYesUpload attachment (base64)
GET/api/items/:id/attachments/:aid/downloadYesDownload file
DELETE/api/items/:id/attachments/:aidYesDelete attachment + blob

Upload Attachment — Body

json
{
  "filename": "report.html",
  "content_type": "text/html",
  "data": "base64-encoded-file-content"
}

Channels

MethodPathAuthDescription
GET/api/channelsYesList channels
POST/api/channelsYesCreate channel

Create Channel — Body

json
{ "name": "deploys", "mode": "open" }

Templates

MethodPathAuthDescription
GET/api/templatesYesList templates
POST/api/templatesYesCreate/upsert template
DELETE/api/templates/:nameYesDelete template

Webhooks

MethodPathAuthDescription
GET/api/webhooksYesList org webhooks
POST/api/webhooksYesCreate webhook
DELETE/api/webhooks/:idYesDelete webhook
POST/api/webhooks/inbound/:tokenNoInbound: external services create items

Create Webhook — Body

json
{
  "name": "slack-notifications",
  "url": "https://hooks.slack.com/...",
  "events": ["item.created", "item.answered"],
  "secret": "optional-hmac-secret"
}

Outbound deliveries include X-Klaxon-Signature: sha256=... and X-Klaxon-Event headers.

Inbound Webhook

See Integrations for the full inbound webhook format including form and callback_url.

Audit

MethodPathAuthDescription
GET/api/auditYesList audit entries (cursor-paginated)

Query params: limit, cursor, item_id (filter by item).

Settings

MethodPathAuthDescription
GET/api/settingsYesList org settings (key-value)
PUT/api/settings/:keyYesUpsert a setting

Set Setting — Body

json
{ "value": true }

Stats

MethodPathAuthDescription
GET/api/statsYesAggregate counts by status, level, channel

Response:

json
{
  "total": 150,
  "open": 23,
  "answered": 89,
  "dismissed": 30,
  "archived": 8,
  "by_level": { "info": 100, "warning": 30, "error": 20 },
  "by_channel": { "deploys": 50, "alerts": 40, "(none)": 60 }
}

Push

MethodPathAuthDescription
POST/api/push/registerYesRegister FCM device token
POST/api/push/unregisterYesRemove device token

Real-time

MethodPathAuthDescription
GET/api/wsYesWebSocket (clients)
GET/mcp/sseYesSSE (MCP agents)
POST/mcpYesMCP JSON-RPC 2.0

Admin — Members

All under /api/admin/*. See Admin Panel.

MethodPathPermissionDescription
GET/api/admin/usersusers.inviteList org members
POST/api/admin/users/:user_id/roleusers.change_roleChange a member's role
POST/api/admin/users/:user_id/deactivateusers.removeRemove a member
GET/api/admin/orgorg.manageFetch org details
PATCH/api/admin/orgorg.manageUpdate org name / slug / metadata
GET/api/admin/usageorg.manageCurrent plan + usage vs. limits
POST/api/admin/agentsagents.manageCreate an API agent (gates on max_agents)

Demoting or removing the only Owner returns 409 Conflict.

Admin — Teams

MethodPathPermissionDescription
GET/api/admin/teamsteams.manage_membersList teams
POST/api/admin/teamsteams.createCreate team (max_teams gate)
PATCH/api/admin/teams/:team_idteams.manage_membersRename / re-slug
DELETE/api/admin/teams/:team_idteams.deleteDelete team
GET/api/admin/teams/:team_id/membersteams.manage_membersList team members
POST/api/admin/teams/:team_id/membersteams.manage_membersAdd member
DELETE/api/admin/teams/:team_id/members/:user_idteams.manage_membersRemove member

Admin — Roles

MethodPathPermissionDescription
GET/api/admin/rolesusers.inviteList system + org-local roles
POST/api/admin/rolesusers.change_roleCreate a custom role (escalation-gated)
PATCH/api/admin/roles/:role_idusers.change_roleEdit a custom role (system roles read-only)
DELETE/api/admin/roles/:role_idusers.change_roleDelete a custom role (fails if members assigned)

Admin — Invitations

Server-side admin routes:

MethodPathPermissionDescription
POST/api/admin/invitationsusers.inviteCreate email or link invite
GET/api/admin/invitationsusers.inviteList active invitations
POST/api/admin/invitations/:id/revokeusers.inviteRevoke

Public routes for invitees (served by klaxon-auth):

MethodPathAuthDescription
GET/auth/invitations/:codeNoPreview (safe to show before sign-in)
POST/auth/invitations/acceptSessionAccept; returns fresh token scoped to org

Accept errors: 403 wrong email, 410 revoked/expired/fully used, 402 plan_limit_exceeded (key max_seats).

Billing

All owner-gated (org.billing).

MethodPathAuthDescription
GET/api/billing/subscriptionYesCurrent plan, status, cancel-at-period-end
POST/api/billing/checkoutYesReturns a Stripe Checkout URL
POST/api/billing/portalYesReturns a Stripe Billing Portal URL
POST/webhooks/stripeHMACStripe webhook (verified via STRIPE_WEBHOOK_SECRET)

Plan-limit gate fires a 402 Payment Required on overage — see Billing.