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 }
  ]
}
    - Key required: include 
?key=PUBLIC_API_KEYor headerx-bc-key: PUBLIC_API_KEY. Default key isbuzzcloud. - Auto-create: collections are created automatically on first POST or GET.
 - Everyone with the key can read/write/delete.
 
Endpoints
| Method | Path | Body | Response | 
|---|---|---|---|
| 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.
- Slug must match 
^[a-z0-9-]{1,48}$. - Name is globally unique (case-insensitive). Conflicts return HTTP 409.
 
// 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
| Route | Serves | Notes | 
|---|---|---|
/ | 
        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
| Variable | Default | Description | 
|---|---|---|
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.