API reference
Every route a Kern server serves. The protocol page explains how these fit together — the handshake, how sync merges changes, and what the realtime socket carries. This page is the index.
Basics
Everything lives under a single base, /kern/v1/, on whatever origin the server
is published at. On the public Kern Cloud that is
https://api.relf.cloud/kern/v1/; on your own server it is
<your address>/kern/v1/.
- All JSON. Errors are
{ code, message }. - Authenticated routes take
Authorization: Bearer <session token>. - The server has no web pages of its own, apart from the invitation hand-off page and share links.
- Logos and avatars are small data URLs, resized by the client.
Three groups of routes behave differently from the rest:
| Group | Authentication |
|---|---|
/instance, /health, /invites/:id, stream endpoints, share links, calendar feeds | Public |
/admin/* | A management token, and an address check |
/inbound/email | The inbound email worker's shared secret |
Accounts and sessions
Better Auth with the bearer plugin: no cookies, no
redirects. A successful sign-in or verification returns the session in a
set-auth-token header.
| Route | What it does |
|---|---|
POST /auth/sign-up/email | Create an account |
POST /auth/sign-in/email | Sign in with a password |
POST /auth/email-otp/send-verification-otp | Send a verification code |
POST /auth/email-otp/verify-email | Verify an address |
POST /auth/sign-in/email-otp | Sign in with a code instead of a password |
POST /auth/email-otp/request-password-reset | Start recovery |
POST /auth/email-otp/reset-password | Finish recovery |
POST /auth/update-user · change-password | Name, avatar, password |
GET /auth/list-sessions · POST /auth/revoke-other-sessions | Your sessions elsewhere |
POST /auth/sign-out · delete-user | End this session · remove the account |
Email verification is required. A 401 on any other route means the session has
ended — suspended, revoked, deleted or expired — and the client should stop retrying and
sign in again.
Your account
GET /me | Account, personal space, workspaces, pending invitations |
POST /me/personal | Create or open the personal space |
Every account has one personal space, created on first use, and can own or join workspaces.
GET /me is what a client calls after signing in to find out what it can open.
Workspaces
GET /workspaces | List |
POST /workspaces | Create: name, slug, description, logo, accent, website, industry, team size, use case, time zone, GitHub organisation, invitations |
GET /workspaces/slug-available?slug= | Availability, with a suggestion |
GET /workspaces/:id | Detail, with members and invitations |
PATCH /workspaces/:id | Update details |
DELETE /workspaces/:id | Delete — owner only |
POST /workspaces/:id/leave | Leave |
POST /workspaces/:id/transfer | Hand ownership to another member |
POST /workspaces/:id/github-synced | Record a GitHub import |
GET /workspaces/:id/activity | Audit log — admins |
Roles run owner (everything, including delete and transfer) ›
admin (details, members, invitations, streams, GitHub import) ›
member (edit everything) › viewer (read only; pushes are
refused).
Members and invitations
GET /workspaces/:id/members | List |
PATCH /workspaces/:id/members/:userId | Role and title |
DELETE /workspaces/:id/members/:userId | Remove |
POST /workspaces/:id/invites | Invite by email |
POST /workspaces/:id/invites/:inviteId/resend | Send again |
DELETE /workspaces/:id/invites/:inviteId | Revoke |
GET /invites/:id | The invitee's view — public |
POST /invites/:id/accept · /decline | Accept or decline |
Accepting an invitation requires a verified email matching the invited address, so an invitation can't be redirected to somebody else. Invitation links are served from a hand-off page that opens the desktop app.
Sync
GET /spaces/:id/sync/pull?since=<n>&limit=<n> | Rows changed after a cursor, in sequence order, with tombstones |
POST /spaces/:id/sync/push | Field-level patches, full rows for new records, and deletions |
GET /spaces/:id/sync/stats | Counts and the current head |
Only core records sync: projects, project groups, boards, board columns, tasks, task labels, labels, milestones, milestone dependencies, release links, notes, inbox items, whiteboards and their nodes and edges, project repositories and task-issue links. Files, folders, repositories, terminals, environment variables, settings and AI keys never leave the computer, and the server refuses tables it doesn't know.
The merge rules — last writer wins per field, deletions win, contiguous sequence numbers — are described in the protocol.
Whiteboard images
POST /spaces/:id/assets | Raw WebP, JPEG or PNG body, up to ASSET_MAX_BYTES |
GET /spaces/:id/assets/:assetId | Fetch one |
GET /spaces/:id/assets | What this space holds |
Images are compressed by the client, de-duplicated per space by hash, counted against
ASSET_QUOTA_MB, and removed after seven days if nothing references them. This
is the one group of routes that doesn't take JSON.
Streams
GET /spaces/:id/streams | List, with presets and limits |
POST /spaces/:id/streams | Create — returns the secret once |
GET /spaces/:id/streams/:streamId | Detail and statistics |
PATCH /spaces/:id/streams/:streamId | Update settings, deep-merged |
DELETE /spaces/:id/streams/:streamId | Delete |
POST …/streams/:streamId/rotate · /reveal · /revoke-previous | Rotation with a grace period; reveal is audited |
PUT …/streams/:streamId/github · POST …/github/check | Write-only GitHub credentials, and a check |
PUT …/streams/:streamId/turnstile | Write-only Turnstile keys |
POST …/streams/:streamId/preview · /test | Dry-run rendering · a real test delivery |
GET …/streams/:streamId/events | Delivery log |
GET/POST …/events/:eventId · /redeliver · /block | One delivery; send it again; block its sender |
DELETE …/streams/:streamId/events | Clear the log |
POST /kern/:streamId | The public endpoint submissions are sent to (OPTIONS for CORS) |
Streams belong to a space and are managed by its owners and admins. Credentials are encrypted at rest and write-only over the API — they go in, and only a rotation or an audited reveal brings a secret back out. What a submission goes through on the way in, and every refusal code, is on the streams page.
Calendar feeds
GET /spaces/:id/calendar-feed | Your private address for this space |
POST /spaces/:id/calendar-feed | Create, or rotate |
DELETE /spaces/:id/calendar-feed | Remove |
The feed itself is a public .ics address, private by virtue of its token. It is
built from the space's records on each fetch: your open tasks with due dates — timed when
they have a time, lasting their estimate — and the space's milestones. It stops working when
rotated or when you leave the space.
Realtime
POST /realtime/ticket | A single-use, 60-second ticket |
GET /realtime?ticket=… | The WebSocket, at the same base with wss:// |
One socket per client. Client to server: subscribe and
unsubscribe for a space, presence, ephemeral and
ping. Server to client: changes, presence,
leave, ephemeral, space, invite,
stream, notice and signed_out.
Presence and ephemeral messages are relayed, never stored. Rooms live in one process, so
running several API instances would need shared pub/sub. A proxy must pass
Upgrade and Connection for any of this to work.
Public endpoints
GET /instance | What this server is: service, protocol, name, registration mode, features, notice |
GET /health | Status, for a load balancer: the database, plus realtime figures |
GET /kern/invite/:id | The hand-off page that opens the app |
POST /kern/:streamId | A stream endpoint |
POST /inbound/email | From the email worker only, with its shared secret |
/instance is a free description of the host to anyone who asks, so it carries
no counts, no component versions and nothing about who has an account there.
Management
/kern/v1/admin/* is a separate surface for administrators, reached with a
management token and, by default, only from the server's own machine. It can read
everything about an instance and none of anybody's work.
It is not an API to build clients against casually — the two clients that exist,
kernctl and Kern Platform, cover it between them,
and the protocol page describes its gates and guarantees.
The authoritative list of routes is the server's own documentation in the Kern-Cloud-Self-Host repository.