Appearance
SSE Backend Requirements — Admin App
The admin app frontend has been updated to use a single SSE stream per app: one organization-level endpoint that delivers both org-level and workspace-level events. This document describes what the backend needs to emit.
Architecture
Admin App (browser)
│
│ GET /sse/organization/{orgSlug}/stream
│ Authorization: Bearer {jwt}
│ X-Client-Id: {uuid}
│
└── Single SSE connection for the entire authenticated session
├── Organization-level events (workspace lifecycle, settings, subscription, members)
└── Workspace-level events (status, settings, members, database, storage, keys, config)The frontend connects when the user selects an organization and disconnects on org switch or logout. There is no separate workspace-level SSE connection in the admin app.
New Endpoint
GET /sse/organization/{orgSlug}/stream
- Auth:
Authorization: Bearer {jwt}header (NOT token-in-URL) - Headers: Accepts
X-Client-Idheader — echo back asoriginClientIdin every event so the frontend can filter own events - Content-Type:
text/event-stream - Scope: All events for the organization and all its workspaces
Event Envelope
Every event must follow this structure:
json
{
"eventType": "organization.workspace.created",
"organisationSlug": "my-org",
"entityType": "Workspace",
"entityId": "uuid-of-entity",
"timestamp": "2026-03-07T12:00:00Z",
"userId": "uuid-of-acting-user",
"originClientId": "uuid-from-x-client-id-header",
"data": {
// Event-specific payload
}
}Event Types
Organization-Level Events
These are new — no existing BE implementation.
organization.workspace.created
Emitted when a workspace is created within the organization.
json
{
"data": {
"workspaceUuid": "string",
"workspaceName": "string",
"workspaceSlug": "string",
"createdByEmail": "string"
}
}organization.workspace.deleted
Emitted when a workspace is deleted.
json
{
"data": {
"workspaceUuid": "string",
"workspaceName": "string"
}
}organization.settings.updated
Emitted when organization name or slug changes.
json
{
"data": {
"name": "string (optional)",
"slug": "string (optional)"
}
}organization.subscription.updated
Emitted when the subscription changes (e.g. Stripe webhook triggers plan change).
json
{
"data": {
"planName": "string",
"status": "string"
}
}organization.member.created
Emitted when a member is added to the organization.
json
{
"data": {
"memberUuid": "string",
"memberEmail": "string",
"memberRole": "string",
"memberStatus": "INVITED | ACTIVE | INACTIVE"
}
}organization.member.updated
Emitted when a member's role or status changes.
json
{
"data": {
"memberUuid": "string",
"memberEmail": "string",
"memberRole": "string",
"memberStatus": "INVITED | ACTIVE | INACTIVE"
}
}organization.member.deleted
Emitted when a member is removed from the organization.
json
{
"data": {
"memberUuid": "string",
"memberEmail": "string"
}
}Dashboard & Stats Events
These are aggregate events — they push fresh computed data so the FE can patch state without a round-trip.
dashboard.updated
Emitted when org-level usage or activity changes (workspace created/deleted, member added/removed, view created/deleted, storage changed). Contains the same data shape as GET /api/users/me dashboard fields.
json
{
"eventType": "dashboard.updated",
"organisationSlug": "acme-store",
"entityType": "Dashboard",
"operation": "UPDATE",
"status": "COMPLETED",
"data": {
"usage": {
"tierName": "PRO",
"tierDisplayName": "Pro",
"workspaceCount": 3,
"maxWorkspaces": 10,
"memberCount": 5,
"maxMembers": 25,
"totalStorageBytes": 104857600,
"storageLimitBytes": 1073741824,
"viewCount": 12
},
"recentActivity": [
{
"type": "SCHEMA_SYNC",
"description": "Schema sync completed for 'products' view",
"workspaceName": "My Workspace",
"timestamp": "2026-03-13T14:30:00Z"
}
]
}
}Trigger points: workspace create/delete, org member add/remove, view create/delete/update (sync), workspace member add/remove, file upload/delete (platform storage).
workspace.stats.updated
Emitted when per-workspace stats change. Delivers viewCount, memberCount, storageBytes, and lastActivity for a single workspace.
json
{
"eventType": "workspace.stats.updated",
"organisationSlug": "acme-store",
"entityType": "Workspace",
"entityId": "workspace-uuid",
"workspaceId": "workspace-slug",
"operation": "UPDATE",
"status": "COMPLETED",
"data": {
"viewCount": 5,
"memberCount": 3,
"storageBytes": 52428800,
"lastActivity": "2026-03-13T14:30:00Z"
}
}Trigger points: view create/delete/update (sync), workspace member add/remove, file upload/delete (platform storage).
Note: Both events are fire-and-forget — they never block the primary operation. They skip broadcasting if no SSE connections exist for the org (getConnectionCount == 0).
Workspace-Level Events (delivered via org stream)
These events are scoped to a specific workspace but delivered through the org-level stream. Some of these already exist on the workspace stream (/sse/workspace/{id}/stream) — they need to also be emitted on the org stream.
Already exist on workspace stream — also emit on org stream:
| Event Type | Payload (data) |
|---|---|
workspace.status.changed | { previousStatus, newStatus } |
workspace.settings.updated | { name?, description?, slug? } |
workspace.member.added | { memberEmail, role } |
workspace.member.removed | { membershipUuid, memberEmail } |
workspace.member.role.changed | { membershipUuid, memberEmail, newRole } |
workspace.database.configured | { databaseName, connectionStatus } |
workspace.storage.configured | {} (empty — FE reloads full config) |
New workspace events — need BE to emit:
| Event Type | Payload (data) |
|---|---|
workspace.apikey.created | { keyUuid, keyName, keyPrefix, createdAt } |
workspace.apikey.rotated | { keyUuid, keyName, keyPrefix } |
workspace.apikey.revoked | { keyUuid } |
workspace.mcpkey.created | { keyUuid, keyName, keyPrefix, createdAt } |
workspace.mcpkey.rotated | { keyUuid, keyName, keyPrefix } |
workspace.mcpkey.revoked | { keyUuid } |
workspace.mcp.configured | { accessMode } |
workspace.api.configured | { maxExpandDepth, corsAllowedOrigins } |
accessMode values: "DISABLED", "READ_ONLY", "READ_WRITE"
corsAllowedOrigins: string[] or null
Echo Filtering
The frontend sends X-Client-Id on connection. The backend must include this as originClientId in every event.
The frontend uses this to filter out its own events for config changes (to avoid overwriting mid-edit form state). Key create/revoke events are NOT filtered — the frontend handles idempotency in reducers.
Summary of BE Work
- New endpoint:
GET /sse/organization/{orgSlug}/streamwith JWT auth +X-Client-Idsupport - Emit 7 org-level event types (workspace lifecycle, settings, subscription, members)
- Emit 2 aggregate event types (
dashboard.updated,workspace.stats.updated) — ✅ implemented - Emit 6 new workspace-level event types on the org stream (API keys, MCP keys, MCP config, API config)
- Mirror 7 existing workspace events onto the org stream (status, settings, members, database, storage)
- Include
originClientIdfromX-Client-Idheader in every event envelope
The existing workspace stream (/sse/workspace/{id}/stream) used by the data app remains unchanged.