Skip to content

Roomful Cloud Architecture (EP-23)

Status: Released · Target: v2.8 ✅

Roomful Cloud adds a hosted multi-tenant relay and management dashboard on top of the open-source relay. The open-source relay (@roomful/relay) remains self-hostable; Cloud is an optional commercial layer.

  1. Open-core: the relay stays MIT-licensed. Cloud adds auth, metering, and dashboard on top.
  2. Vendor-agnostic metering: usage events are structured, not tied to Stripe/AWS.
  3. Progressive adoption: start with free tier, upgrade to paid; same relay binary.
  4. Tenant isolation at the edge: auth happens before any room join.
┌─────────────────────────────────────────────────────┐
│ Cloud Dashboard │
│ (Web UI — projects, keys, usage, billing, alerts) │
└──────────────────────┬──────────────────────────────┘
│ REST API
┌──────────────────────▼──────────────────────────────┐
│ Cloud API Server │
│ • Auth (API keys / JWT) │
│ • Project / Room / Quota CRUD │
│ • Usage aggregation & query │
│ • Webhook dispatch │
└──────────────────────┬──────────────────────────────┘
┌──────────────────────▼──────────────────────────────┐
│ Cloud Relay Gateway │
│ • API key validation (edge) │
│ • Tenant routing (project → relay instance) │
│ • Rate limiting (per-project / per-room) │
│ • Usage event emission │
└──────────────────────┬──────────────────────────────┘
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Relay 1 │ │ Relay 2 │ │ Relay N │
│ (shard) │ │ (shard) │ │ (shard) │
└─────────┘ └─────────┘ └─────────┘
ComponentWhat it doesExisting?
Cloud DashboardWeb UI for project/team/usage managementNew — apps/dashboard
Cloud API ServerREST API backing the dashboard + programmatic accessNew — packages/cloud-api
Cloud Relay GatewayAuth + routing + metering edge layerNew — extends @roomful/relay
Relay ShardStandard @roomful/relay instance, unchanged✅ Exists
Usage StoreTime-series usage eventsNew — PostgreSQL/TimescaleDB
Organization
id: uuid
name: string
slug: string (unique, URL-safe)
plan: 'free' | 'pro' | 'enterprise'
created_at: timestamp
Project
id: uuid
org_id: uuid → Organization
name: string
slug: string (unique per org)
relay_url: string (assigned relay shard)
quota_rooms: int (max concurrent rooms, default 10)
quota_peers_per_room: int (default 50)
quota_messages_per_minute: int (default 1000)
quota_storage_mb: int (default 100)
created_at: timestamp
ApiKey
id: uuid
project_id: uuid → Project
name: string (label)
key_prefix: string (first 8 chars, for display)
key_hash: string (bcrypt/sha256)
scopes: jsonb (['rooms:read', 'rooms:write', 'admin'])
expires_at: timestamp?
last_used_at: timestamp?
created_at: timestamp
revoked_at: timestamp?
Room (logical, not the relay-internal room)
id: uuid
project_id: uuid → Project
room_id: string (the actual room identifier)
status: 'active' | 'idle' | 'closed'
peer_count: int
message_count: int
created_at: timestamp
last_activity_at: timestamp
UsageEvent
id: uuid
project_id: uuid → Project
room_id: string
event_type: enum (see below)
quantity: float
unit: string
metadata: jsonb
recorded_at: timestamp
event_typeunitDescription
room.minuteminutesWall-clock time a room was active
peer.connectionconnectionsUnique peer connection
message.sentmessagesRelay messages forwarded
storage.bytebytesDurable storage consumed (comments, state)
recording.minuteminutesSession recording duration
ai.actionactionsAI agent action executed
roomful_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
│ │ │ │
│ │ └─ 48 random chars (base62)
│ └────── environment prefix (live | test)
└────────────── service identifier
1. Client connects with ?api_key=roomful_live_xxx
2. Gateway extracts key, hashes, looks up in cache/DB
3. If valid: resolve project → relay shard → proxy WebSocket
4. If invalid: 401 + close connection
5. On every message: check rate limit (per-project, per-room)
6. Emit usage event for billing
POST /v1/projects/:projectId/keys Create key
GET /v1/projects/:projectId/keys List keys
DELETE /v1/projects/:projectId/keys/:keyId Revoke key
TierRoomsPeers/RoomMsgs/MinStorage
Free52550050 MB
Pro5020010,0005 GB
EnterpriseCustomCustomCustomCustom
  • Gateway-level: token bucket per project, per room
  • Relay-level: existing --max-rooms, --max-room-size flags
  • Exceeded: 429 + X-RateLimit-Reset header
POST /v1/auth/token Exchange API key for JWT (dashboard sessions)
GET /v1/projects List projects for org
POST /v1/projects Create project
GET /v1/projects/:id Get project details
PATCH /v1/projects/:id Update project
DELETE /v1/projects/:id Delete project
GET /v1/projects/:id/rooms List active rooms
GET /v1/projects/:id/rooms/:roomId Room details + metrics
GET /v1/projects/:id/usage?from=X&to=Y&granularity=hour|day|month
GET /v1/projects/:id/keys List keys (prefix + metadata, never full key)
POST /v1/projects/:id/keys Create key (returns full key once)
DELETE /v1/projects/:id/keys/:keyId Revoke key

Phase 1: Data Model + API Keys (this sprint)

Section titled “Phase 1: Data Model + API Keys (this sprint)”
  • packages/cloud-api: data model (SQL migrations), API key CRUD
  • Gateway prototype: API key validation at edge
  • Usage event emission in gateway
  • Usage aggregation queries
  • Quota enforcement
  • apps/dashboard: minimal web UI
  • Project management, key management, usage charts
  • Multi-region relay sharding
  • Enterprise packaging (#234)
#DecisionOptions
D1DatabasePostgreSQL (with TimescaleDB for usage) vs SQLite for simplicity
D2Relay shardingDNS-based routing vs gateway proxy vs consistent hashing
D3Gateway runtimeNode.js (same stack) vs Cloudflare Workers (edge) vs Envoy
D4Webhook deliveryDirect HTTP POST vs message queue (Redis Streams)