Krispy AI
reference

edge routes / API

every HTTP route on the edge Worker โ€” method, path, shape, auth.

edge routes

one Cloudflare Worker hosts all of these plus the SessionDO. CORS allow-origin is ALLOWED_ORIGIN (default *); OPTIONS is answered for every route.

public routes

methodpathbody / query โ†’ response
POST/api/chat{ sessionId, message, tenantId?, history? } โ†’ { reply, handoff, handedOff, degraded?, formId?, form? }
POST/api/contact{ sessionId, tenantId?, name?, contact?, message? } โ†’ { ok: true } โ€” legacy contact-capture shim (maps onto the lead fan-out)
POST/api/lead{ tenantId?, sessionId, formId?, values, history } โ†’ { ok: true } โ€” a form's captured values fan out to delivery connectors
GET/api/session/:id/ws?t=<tenant>WebSocket upgrade โ†’ SessionDO. 426 if not an upgrade. the visitor's live channel
GET/api/widget/config?t=<tenant>โ†’ { theme: {...} } โ€” the public, secret-free widget projection (whitelist only)
GET/api/usage?t=<tenant>โ†’ { tenantId, usage: { ai, handoff, tokens }, plan, entitled, status, limits, withinLimits }
GET/healthโ†’ { status: "ok", service: "edge" }

POST /api/chat gate

before answering, the Worker checks entitlement + plan limits. self-host ("self") is always entitled and unmetered. a non-self tenant with no valid snapshot gets 402 { error: "subscription_required" }; over its cap it gets 429 { error: "usage_limit_reached" }. missing sessionId/message โ†’ 400.

secret-guarded routes

these carry a bot token or mutate config, so they require a shared-secret header. a missing or wrong secret returns 401 (config routes) / 403 (webhook, billing) and leaks nothing.

methodpathauth headerpurpose
POST/api/telegram/webhookx-telegram-bot-api-secret-token: <TELEGRAM_WEBHOOK_SECRET>owner's Telegram reply โ†’ push to the visitor via the DO, set handedOff
GET/api/tenant/config?t=<tenant>x-tenant-sync-secret: <TENANT_SYNC_SECRET>read a tenant's full config { botToken, chatId, systemPrompt?, model?, ... } (404 if none)
POST/api/tenant/configx-tenant-sync-secret: <TENANT_SYNC_SECRET>{ tenantId, config } โ†’ merge into the tenant's KV config. the CLI writes here
POST/api/billing/entitlementx-billing-sync-secret: <BILLING_SYNC_SECRET>mirror an entitlement snapshot into KV (for a hosted/multi-tenant deploy; unused in self-host)

GET /api/tenant/config returns the full config (bot token included) and is secret-guarded. GET /api/widget/config is the public read the browser uses โ€” it returns only the theme whitelist and never the token/prompt. don't confuse them. see security.

the SessionDO (internal)

the Worker forwards to a SessionDO per (tenant, session); the browser never calls it directly. its surface: the WebSocket upgrade (visitor connects, receives { type: "ready", handedOff }), GET /state ({ handedOff }), POST /operator {text} (set handedOff, broadcast the operator reply), and POST /handoff (broadcast a handoff prompt). server โ†’ browser events: ready, operator, handoff, resume.

keeping this in sync

the repo mirrors routes into a Bruno collection (api-collection/). when you add or change a route, update the matching .bru request in the same change (per CONTRIBUTING.md).

On this page