Skip to content
GitHub
Get started →

How Spelo works

Spelo is a hosted SaaS with four surfaces:

SurfaceRoleRuntime
WidgetThe orb that runs on your websiteUser’s browser
APIAuth, site config, token minting, query proxyapi.spelo.ai (Hono/Node)
DashboardWhere you configure your siteapp.spelo.ai (Next.js)
DocsThis sitedocs.spelo.ai (Starlight)

The data flow of a single voice query

Your website
<script src="spelo.ai/spelo.js"
data-site-id="abc123" async></script>
┌───────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
CDN Edge User's Browser Spelo API
(spelo.js) (widget) (api.spelo.ai)
│ │
│ GET /v1/abc123/config
│ ────────────────►│
│ │ ←── Supabase (site_configs)
│ ◄────────────────│
│ │
│ POST /v1/abc123/token
│ ────────────────►│
│ │ ←── decrypt OpenAI key
│ │ + OpenAI /realtime
│ ◄────────────────│
│ │
│ WebRTC direct to OpenAI Realtime
│ ◄──────────────────────────────────►
│ │
│ POST /v1/abc123/query
│ (search_database function call)
│ ────────────────►│
│ │ ←── Your DB (via adapter)
│ ◄────────────────│

Three key properties fall out of this shape:

  1. Audio never touches our servers. Browser and OpenAI speak WebRTC directly. We only hand out short-lived session tokens.
  2. Your database credentials never reach the browser. The widget asks our API, our API asks your database (with your read-only user), and the results come back through us.
  3. Every call is scoped to your site_id. Origin is verified server-side against the domains you registered. A stolen snippet does not work from another domain.

What the AI can do

The widget registers a small set of function tools with OpenAI. When the model decides to call one, it arrives over the WebRTC data channel and the widget executes it locally (or proxies to our API).

ToolWhat it doesRuns in
navigateChanges the URL via history.pushState or location.assignBrowser
scroll_toSmooth-scrolls to a DOM element or section idBrowser
click_elementClicks a button, link, or accordion the user asked aboutBrowser
fill_fieldFills a form input the user dictatedBrowser
Image analysisDisabled for now; the agent says it cannot see or analyze imagesN/A
search_databaseQueries your data through an adapterAPI proxy

The widget announces which tools are available at session start, pulled from your site’s site-intelligence payload.

Multi-tenancy

Every resource is scoped by site_id. When a request comes in:

  1. The API looks up the site config (cached 60s)
  2. It validates the request Origin matches a registered domain
  3. It decrypts your OpenAI key (or uses ours on the managed plan)
  4. It routes the request

The widget never knows your OpenAI key, database credentials, or OAuth tokens. Those only exist in the API server.

Session lifecycle

  • A session begins when a visitor clicks the orb and grants mic access.
  • The widget POSTs to /v1/:siteId/token and receives an ephemeral token (TTL 2 hours, signing secret included).
  • WebRTC connects browser → OpenAI Realtime. Audio streams both ways.
  • The session survives client-side navigation: the widget rehydrates from sessionStorage, so SPA route changes do not drop the call.
  • The session ends when the visitor closes the tab, clicks the orb again to hang up, or two hours pass.

Where state lives

DataWhereRetention
Audio (both directions)Browser ↔ OpenAINot stored
TranscriptsSpelo SupabaseDefault 30 days, configurable 0–90
Session metadata (duration, function calls)Spelo Supabase12 months
Your database contentsYour databaseWe never copy it
Site config (colors, personality, pronunciations)Spelo SupabaseUntil you delete the site

Security summary

  • CORS per registered domain (no wildcards)
  • AES-256-GCM encryption for all customer secrets at rest
  • HMAC-SHA256 request signing on /query
  • Read-only enforcement at three layers: DB user, adapter code, field whitelist
  • Ephemeral tokens scoped to ~2 hours

Full detail in Resources → Security.

Deployment targets

ComponentHost
APIRailway / Fly.io (Node) or Cloudflare Workers
DashboardVercel
MarketingVercel
DocsVercel
Widget CDNCloudflare (spelo.js) + S3 origin
DatabaseSupabase Postgres
Redis (rate limits)Upstash

Further reading