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:

GroupAuthentication
/instance, /health, /invites/:id, stream endpoints, share links, calendar feedsPublic
/admin/*A management token, and an address check
/inbound/emailThe 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.

RouteWhat it does
POST /auth/sign-up/emailCreate an account
POST /auth/sign-in/emailSign in with a password
POST /auth/email-otp/send-verification-otpSend a verification code
POST /auth/email-otp/verify-emailVerify an address
POST /auth/sign-in/email-otpSign in with a code instead of a password
POST /auth/email-otp/request-password-resetStart recovery
POST /auth/email-otp/reset-passwordFinish recovery
POST /auth/update-user · change-passwordName, avatar, password
GET /auth/list-sessions · POST /auth/revoke-other-sessionsYour sessions elsewhere
POST /auth/sign-out · delete-userEnd 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 /meAccount, personal space, workspaces, pending invitations
POST /me/personalCreate 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 /workspacesList
POST /workspacesCreate: 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/:idDetail, with members and invitations
PATCH /workspaces/:idUpdate details
DELETE /workspaces/:idDelete — owner only
POST /workspaces/:id/leaveLeave
POST /workspaces/:id/transferHand ownership to another member
POST /workspaces/:id/github-syncedRecord a GitHub import
GET /workspaces/:id/activityAudit 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/membersList
PATCH /workspaces/:id/members/:userIdRole and title
DELETE /workspaces/:id/members/:userIdRemove
POST /workspaces/:id/invitesInvite by email
POST /workspaces/:id/invites/:inviteId/resendSend again
DELETE /workspaces/:id/invites/:inviteIdRevoke
GET /invites/:idThe invitee's view — public
POST /invites/:id/accept · /declineAccept 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/pushField-level patches, full rows for new records, and deletions
GET /spaces/:id/sync/statsCounts 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/assetsRaw WebP, JPEG or PNG body, up to ASSET_MAX_BYTES
GET /spaces/:id/assets/:assetIdFetch one
GET /spaces/:id/assetsWhat 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/streamsList, with presets and limits
POST /spaces/:id/streamsCreate — returns the secret once
GET /spaces/:id/streams/:streamIdDetail and statistics
PATCH /spaces/:id/streams/:streamIdUpdate settings, deep-merged
DELETE /spaces/:id/streams/:streamIdDelete
POST …/streams/:streamId/rotate · /reveal · /revoke-previousRotation with a grace period; reveal is audited
PUT …/streams/:streamId/github · POST …/github/checkWrite-only GitHub credentials, and a check
PUT …/streams/:streamId/turnstileWrite-only Turnstile keys
POST …/streams/:streamId/preview · /testDry-run rendering · a real test delivery
GET …/streams/:streamId/eventsDelivery log
GET/POST …/events/:eventId · /redeliver · /blockOne delivery; send it again; block its sender
DELETE …/streams/:streamId/eventsClear the log
POST /kern/:streamIdThe 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.

Share links

GET /sharesYour live links
PUT /sharesPublish or update: a note, a whiteboard image, or a project
GET /shares/for/:kind/:refIdLook up the link for an item
DELETE /shares/:idTurn it off

Read-only snapshots the owner publishes. One live link per item; publishing again updates it. Notes render Markdown without raw HTML and with only web and mail links. The pages run no scripts and aren't indexed. SHARES_ENABLED=false turns the whole feature off.

Calendar feeds

GET /spaces/:id/calendar-feedYour private address for this space
POST /spaces/:id/calendar-feedCreate, or rotate
DELETE /spaces/:id/calendar-feedRemove

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/ticketA 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 /instanceWhat this server is: service, protocol, name, registration mode, features, notice
GET /healthStatus, for a load balancer: the database, plus realtime figures
GET /kern/invite/:idThe hand-off page that opens the app
POST /kern/:streamIdA stream endpoint
POST /inbound/emailFrom 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.