Quickstart

All examples assume same-origin (the HTML/JS is served by the same BuzzCloud server). Public DB requests require the key. Default key is buzzcloud.

// list items
fetch('/api/db/chat_global?key=buzzcloud').then(r=>r.json()).then(console.log);

// create item
fetch('/api/db/chat_global?key=buzzcloud',{
  method:'POST',
  headers:{'Content-Type':'application/json'},
  body: JSON.stringify({ username:'Guest', text:'hello' })
}).then(r=>r.json()).then(console.log);

// update item
fetch('/api/db/chat_global/ITEM_ID?key=buzzcloud',{
  method:'PUT',
  headers:{'Content-Type':'application/json'},
  body: JSON.stringify({ text:'edited' })
}).then(r=>r.json());

// delete item
fetch('/api/db/chat_global/ITEM_ID?key=buzzcloud',{ method:'DELETE' })
  .then(r=>r.json());

Public Realtime Database /api/db/:collection

Global JSON collections stored on disk under DATABASE_ROOT (default: <DECK_ROOT>/database). Each collection is <collection>.json with shape:

{
  "items": [
    { "id":"a1b2c3...", "yourField": "...", "created_at": 1730500000000, "updated_at": 1730500000000 }
  ]
}

Endpoints

MethodPathBodyResponse
GET /api/db/<collection>?key=buzzcloud { items: [...] }
POST /api/db/<collection>?key=buzzcloud JSON object (your fields) Created item + id, created_at, updated_at
PUT /api/db/<collection>/<id>?key=buzzcloud Partial JSON Updated item
DELETE /api/db/<collection>/<id>?key=buzzcloud { ok:true } or { error:"Not found" }

SSE Realtime /api/rt/subscribe

Subscribe to change notifications for a single collection via Server-Sent Events (EventSource).

// Browser example
const KEY = 'buzzcloud';
const coll = 'chat_global';

const es = new EventSource(
  `/api/rt/subscribe?collection=${encodeURIComponent(coll)}&key=${encodeURIComponent(KEY)}`
);

// Fires once on connect
es.addEventListener('hello', (e) => {
  console.log('hello', JSON.parse(e.data));
});

// Full current list (fires after hello)
es.addEventListener('snapshot', (e) => {
  const { items } = JSON.parse(e.data);
  console.log('snapshot size', items.length);
});

// On any write to the collection (POST/PUT/DELETE)
es.onmessage = async (e) => {
  console.log('update notice', JSON.parse(e.data));
  // Typical pattern: re-fetch the list
  const list = await fetch(`/api/db/${coll}?key=${KEY}`).then(r=>r.json());
  // ...redraw UI...
};

// If EventSource errors (e.g., cross-origin or file://), fall back:
es.onerror = () => {
  console.warn('SSE failed; fallback to polling');
  setInterval(async ()=>{
    const list = await fetch(`/api/db/${coll}?key=${KEY}`).then(r=>r.json());
    // ...redraw UI...
  }, 1000);
};

Tip: Open your pages via the BuzzCloud server (same origin). Opening HTML as file:// often breaks SSE due to origin rules; use the polling fallback.

Use-Case Examples

1) Simple Chat Wall (collection: chat_global)

// Post a message
await fetch('/api/db/chat_global?key=buzzcloud',{
  method:'POST', headers:{'Content-Type':'application/json'},
  body: JSON.stringify({ username:'Guest', text:'Hi!' })
});

// Subscribe + re-fetch on change (see SSE section)

2) Lightweight Multiplayer State (collection: game_players)

// Create your player
const me = await fetch('/api/db/game_players?key=buzzcloud',{
  method:'POST', headers:{'Content-Type':'application/json'},
  body: JSON.stringify({ name:'Player', x:0, y:0 })
}).then(r=>r.json());

// Move updates while key held (throttle ~10/s)
function sendPos(x,y){
  fetch(`/api/db/game_players/${me.id}?key=buzzcloud`,{
    method:'PUT', headers:{'Content-Type':'application/json'},
    body: JSON.stringify({ x, y })
  });
}

// Subscribe to updates + re-GET game_players on message to render everyone

Remember: this is a shared public board. Anyone with the key can modify any item. Do not store private info or assume per-user security.

Sites API (Authenticated) /api/sites

After login (cookie bc_token), users can create/edit static pages at /website/<slug>.html.

// Create
POST /api/sites
{ "name":"My Page", "slug":"mypage", "content":"..." }

// Get list
GET /api/sites

// Update (can change name/slug/content)
PUT /api/sites/:id

// Delete
DELETE /api/sites/:id

These endpoints require you to be logged in (via /api/auth/login). The cookie is HTTP-only and sent automatically by the browser.

Auth (Dashboard only) /api/auth/*

// Sign up
POST /api/auth/signup
{ "name":"User", "email":"[email protected]", "password":"supersecret123" }

// Login
POST /api/auth/login
{ "email":"[email protected]", "password":"supersecret123" }

// Current user
GET /api/auth/me

// Logout
POST /api/auth/logout

Public DB (/api/db/*) does NOT use login—only the shared key.

Media / Streaming /movies /stream/:filename /page/:basename

// List discovered media files
GET /movies
-> [ { "filename":"clip.mp4", "url":"/stream/clip.mp4" }, ... ]

// Stream directly (Range supported)
GET /stream/clip.mp4

// Auto player page (or serves /movies/basename.html if present)
GET /page/clip

Static Routes

RouteServesNotes
/ landing.html from HOME_DIR Main landing page.
/home home.html from HOME_DIR Owner dashboard shell.
/status status.html from HOME_DIR Uses /api/health/*.
/website Static sites under WEBSITES_PATH Serves index.html or home.html if present.
/website/<slug>.html The saved site file Managed via Sites API.

Environment Variables

VariableDefaultDescription
PORT 3001 HTTP port for the Express app.
DECK_ROOT <repo>/.. (server dir parent) Root folder for home/websites/movies/database.
HOME_DIR <DECK_ROOT>/home Where landing.html, home.html, status.html live.
WEBSITES_PATH <DECK_ROOT>/websites User site files (served under /website).
MOVIES_PATH <DECK_ROOT>/movies Audio/video files for listing/streaming.
DATABASE_ROOT <DECK_ROOT>/database Directory for public JSON collections (each as <collection>.json).
PUBLIC_API_KEY buzzcloud Required for all /api/db/* and /api/rt/subscribe.
JWT_SECRET dev-secret-change-me Signs the bc_token cookie for Sites API auth.
SECURE_COOKIES false Set true to mark cookies secure (HTTPS only).
DB_FILE <DECK_ROOT>/secure/buzzcloud.db SQLite file for users and site index.

AI Usage Prompt

Paste this into another assistant so it uses the API exactly (no hallucinated endpoints). It assumes the default public key buzzcloud.

You are building a browser app that talks to a self-hosted backend called BuzzCloud.

Rules:
- Same-origin requests only (the HTML/JS is served by the same server).
- Use ONLY these endpoints. Do not invent routes.
- Public DB requires the key “buzzcloud” on EVERY request (either ?key=buzzcloud or header x-bc-key: buzzcloud).

PUBLIC REALTIME DB (NO LOGIN)
- GET  /api/db/:collection?key=buzzcloud        -> { items:[...] }
- POST /api/db/:collection?key=buzzcloud        body: JSON object -> created item (adds id, created_at, updated_at)
- PUT  /api/db/:collection/:id?key=buzzcloud    body: partial JSON -> updated item
- DELETE /api/db/:collection/:id?key=buzzcloud  -> { ok:true } or { error:"Not found" }
Notes:
- Collections are created automatically on first POST/GET.
- Data is stored on disk inside DATABASE_ROOT as <collection>.json.
- Anyone with the key can modify any collection. Do not store secrets.

REALTIME (SSE)
- Subscribe to one collection:
  GET /api/rt/subscribe?collection=<name>&key=buzzcloud
  Browser:
    const es = new EventSource('/api/rt/subscribe?collection=' + encodeURIComponent(coll) + '&key=buzzcloud');
    es.addEventListener('hello',    (e)=>{ /* 1-time */ });
    es.addEventListener('snapshot', (e)=>{ /* full current list */ });
    es.onmessage = async (e)=>{ /* refetch GET /api/db/<coll>?key=buzzcloud and redraw */ };
  If SSE fails (e.g., file://), fall back to polling GET every ~1s.

SITES API (AUTH REQUIRED)
- Requires login cookie (bc_token) set by:
  POST /api/auth/signup { name,email,password }
  POST /api/auth/login  { email,password }
- Sites endpoints:
  GET    /api/sites
  GET    /api/sites/:id
  POST   /api/sites      { name, slug, content }
  PUT    /api/sites/:id  { name?, slug?, content? }
  DELETE /api/sites/:id
Constraints:
- slug: ^[a-z0-9-]{1,48}$
- name: globally unique (case-insensitive). Conflicts -> HTTP 409.

MEDIA
- GET /movies -> list files
- GET /stream/:filename -> stream with Range
- GET /page/:basename   -> serve custom /movies/<basename>.html or auto player page

STATIC
- /         -> landing.html (HOME_DIR)
- /home     -> home.html
- /status   -> status.html (uses /api/health/*)
- /website  -> serves websites folder (index.html or home.html if present)
- /website/<slug>.html -> a saved site

YOUR JOB:
- For any /api/db/* request, always include ?key=buzzcloud.
- Use SSE first; if it errors, fall back to 1s polling.
- Do not add auth flows to public DB. It is key-only by design.