Chat API
Two routes, one auth/scoping model: /api/chat (one-shot) and /api/chat-stream (streaming,
with memory and a real tool loop).
POST /api/chat
{ "question": "What did Lindsey promise about launch?", "as": "optional-viewer-id" }
Scoped Q&A over the live spine. Identity comes from the verified Bearer JWT; the model is shown
only the projects that viewer may see, resolved server-side through the same _spine.js boundary
/api/spine uses — the client cannot widen its own scope by passing a different as. A lead or
admin may pass as to ask as someone else, for oversight; anyone else's as is ignored. This
route is read-only and never writes. With no Supabase configured, it falls back to the token-gated
bundled demo dataset rather than erroring.
Model: chat role (claude-haiku-4-5 by default) — see
AI Providers Reference.
POST /api/chat-stream
{ "question": "...", "session_id": "uuid", "as": "optional-viewer-id" }
Server-Sent Events out: {"type": "delta" | "tool" | "done" | "error", ...}. Auth and scoping
mirror /api/chat exactly, but this route adds two things the one-shot route doesn't have:
persisted multi-turn memory (chat_sessions/chat_messages) and a real tool-calling loop instead
of a single injected context block. Read-only tools run over the scoped spine; list_skills,
read_skill, and propose_skill_change are dispatched at the route boundary rather than inside
the generic tool executor, specifically so a skill proposal always passes through the same
prompt-governance path a prompt rule proposal does.
Model: chatStream role by default (claude-sonnet-5), upgraded to chatStreamPro
(claude-opus-5) only for callers on the SABLE_CHAT_OPUS_EMAILS allowlist.
Rate limiting
Both routes are rate-limited independently: SABLE_CHAT_RATE_LIMIT for /api/chat,
SABLE_CHAT_STREAM_RATE_LIMIT for /api/chat-stream. SABLE_CHAT_CTX_MAX_CHARS caps the scoped
spine context injected into either route so it can't exceed the model's context window (default
120,000 characters).
Public-web research stays separate
Neither chat route reaches the open web. /api/research (Exa-backed, lead/admin-only) is a
separate, explicitly invoked surface — see AI Providers Reference.
Private SABLE knowledge is the only source either chat route draws from.
Where the code lives
sable-agents-demo/web/api/chat.js,chat-stream.js,_chat_context.js,_chat_stream_core.jssable-agents-demo/web/api/_spine.js,_scope.js— the scope boundary both routes share with/api/spinesable-agents-demo/web/api/_propose_rule.js,_propose_skill.js