๐Ÿš€ Quick Start

Kraken provides WebSocket-based real-time sync for Yjs documents. Each app gets isolated namespaces for data separation.

1. Register Your App

Before connecting, your app must be registered with Kraken (contact admin or use the API):

POST /api/apps
Authorization: Bearer YOUR_ADMIN_TOKEN
Content-Type: application/json

{
  "app_id": "my-app",
  "name": "My Application",
  "allowed_origins": ["https://my-app.example.com"],
  "owner_email": "dev@example.com"
}

2. Connect via WebSocket

const ws = new WebSocket(
  'wss://kraken.helixpods.ai/doc/my-document?token=YOUR_ROOM_JWT'
);

๐Ÿ” Authentication

Kraken uses a three-layer security model:

Layer 1: Per-App API Key (server-side only)

Each registered app gets a krk_โ€ฆ API key (the platform master token is admin-only). Your backend uses it to mint short-lived room JWTs via POST /api/tokens (Authorization: Bearer krk_โ€ฆ). A per-app key can only mint tokens for its own app. Never send this key to a browser or use it on a WebSocket.

Layer 2: JWT Room Tokens (WebSocket)

Short-lived HS256 JWTs minted from POST /api/tokens. Pass the JWT as:

  • Query parameter: ?token=YOUR_ROOM_JWT
  • Header: Authorization: Bearer YOUR_ROOM_JWT

Layer 3: Origin-Based Namespacing

Your app's origin (domain) determines which namespace your rooms belong to. This is automatic - Kraken reads the Origin header from WebSocket connections.

Important: Data isolation is enforced by origin-based namespacing plus per-app JWT app claims. Per-app API keys and the master token are server-side credentials โ€” only room JWTs reach the client.

๐Ÿข Multi-Tenancy

Each registered app gets isolated data:

Your Request Internal Room ID
/doc/readme from app-a.example.com app-a:readme
/doc/readme from app-b.example.com app-b:readme
Automatic Isolation: Apps cannot access each other's documents, even with the same room name.

๐Ÿ“ก API Reference

GET /health

Health check endpoint. No authentication required.

curl https://kraken.helixpods.ai/health

WS /doc/:roomName

WebSocket endpoint for Yjs sync. Requires authentication.

wss://kraken.helixpods.ai/doc/my-room?token=TOKEN

GET /api/apps

List registered apps. Admin authentication required.

POST /api/apps

Register a new app. Master-token auth. Returns a one-time krk_ API key.

{
  "app_id": "string (required)",
  "name": "string (required)",
  "allowed_origins": ["array of strings (required)"],
  "description": "string (optional)",
  "owner_email": "string (optional)"
}

PATCH /api/apps/:app_id

Update an app. Master token, or the app's own key for its own record ({ "rotate_api_key": true } returns a fresh key).

POST /api/tokens

Issue a room/app-scoped JWT for WebSocket sync. Auth: master token (any app) or the app's own krk_ key (own app only).

{
  "app_id": "string (required)",
  "scope": "read | write (optional, default write)",
  "room": "string (optional room constraint)",
  "ttl_seconds": "number (optional, max 30 days)"
}

GET /api/stats

Server statistics. Master-token auth only.

GET /api/rooms

List rooms. Master-token auth only.

๐Ÿ’ป Client Integration

Using y-websocket (Recommended)

import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';

const doc = new Y.Doc();
// Kraken routes document sync under /doc/{room}
const provider = new WebsocketProvider(
  'wss://kraken.helixpods.ai/doc',
  'my-document',
  doc,
  { params: { token: 'YOUR_ROOM_JWT' } } // room JWT from your backend, never a server credential
);

provider.on('status', ({ status }) => {
  console.log('Connection status:', status);
});

// Use your Yjs document
const text = doc.getText('content');
text.insert(0, 'Hello, collaborative world!');

Using Native WebSocket

import * as Y from 'yjs';
import * as syncProtocol from 'y-protocols/sync';
import * as encoding from 'lib0/encoding';
import * as decoding from 'lib0/decoding';

const doc = new Y.Doc();
const ws = new WebSocket('wss://kraken.helixpods.ai/doc/my-room?token=YOUR_TOKEN');
ws.binaryType = 'arraybuffer';

ws.onopen = () => {
  // Send sync step 1
  const encoder = encoding.createEncoder();
  encoding.writeVarUint(encoder, 0); // message type: sync
  syncProtocol.writeSyncStep1(encoder, doc);
  ws.send(encoding.toUint8Array(encoder));
};

ws.onmessage = (event) => {
  const decoder = decoding.createDecoder(new Uint8Array(event.data));
  const messageType = decoding.readVarUint(decoder);
  // Handle sync messages...
};

React Example

import { useEffect, useState } from 'react';
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';

function useYjsDocument(roomName: string, roomJwtFromYourBackend: string) {
  const [doc] = useState(() => new Y.Doc());
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    const provider = new WebsocketProvider(
      'wss://kraken.helixpods.ai/doc',
      roomName,
      doc,
      // Room JWT minted by YOUR backend via POST /api/tokens.
      // Server credentials (per-app API key / master token) never reach the browser.
      { params: { token: roomJwtFromYourBackend } }
    );

    provider.on('status', ({ status }) => {
      setConnected(status === 'connected');
    });

    return () => provider.destroy();
  }, [roomName, doc]);

  return { doc, connected };
}

๐Ÿ“ฆ Client SDK Status

Use @nexartis/kraken-sdk (repo: Nexartis/kraken-sdk) for SvelteKit and Svelte 5 applications requiring real-time document sync.

โš ๏ธ Error Codes

CodeMeaningSolution
400Bad RequestInvalid room path
401UnauthorizedMissing/invalid JWT or admin credential
403ForbiddenOrigin not registered
426Upgrade RequiredUse WebSocket, not HTTP