Surfaces
A surface is a channel where people talk to an agent. Seven are wired: WhatsApp, iMessage, Slack, Discord, Telegram, voice and web chat. Replies go back on the channel the message came from.
Available surfaces
Two paths:
- Personal (bridge) -- a Baileys-based bridge pairs with your own WhatsApp account by QR code. No Business account needed. Self-hosted only.
- Business Cloud API -- the surface gateway has a WhatsApp Cloud API adapter for a dedicated business number.
To set up personal WhatsApp:
- Open Channels (
/surfaces) and choose WhatsApp - Select Personal (bridge)
- Scan the QR code with your WhatsApp app
- The bridge connects and your agent is now reachable via WhatsApp
make run-whatsapp-bridge, or lantern dev, which starts it by default.Bridge tenant env var
Set LANTERN_TENANT_ID to the tenant UUID the bridge runs under. LANTERN_DEFAULT_TENANT_ID is still read as a fallback. The iMessage bridge and bridge-core resolve it in that order. The macOS iMessage bridge is covered in Personal assistant.
Slack
The surface gateway (services/surface-gateway) has a Slack adapter for events and bot messages, and the control plane serves a slash-command endpoint at POST /v1/surfaces/slack/command. In production, unsigned slash commands are refused unless SLACK_SIGNING_SECRET is set. Configure the surface under Channels (POST /v1/surfaces with a surfaceId and displayName).
Discord
A Discord adapter ships in the surface gateway. Configure it under Channels with your bot credentials.
Telegram
Connect a Telegram bot to an agent:
- Create a bot via
@BotFatheron Telegram - Add the bot token under Channels
- Assign the surface to an agent
Voice (Twilio + LiveKit)
Phone numbers route inbound calls to a Lantern agent. Two providers are supported:
- Twilio -- TwiML webhooks for inbound PSTN calls. The control-plane verifies Twilio's webhook signature and replies with TwiML. SMS is also available on the same Twilio connector.
- LiveKit -- realtime audio; the control-plane mints a short-lived join token and the audio loop runs in a separately-deployed LiveKit Agents worker (media last-mile).
Budget gating
Voice spend counts against the same agent_budgets as runs. A Twilio inbound call over a hard-fail budget is declined with <Reject> (no carrier cost). A LiveKit join token is refused with HTTP 402 (no token means no media). A flat cost estimate is recorded on connect; the provider's status callback at POST /v1/voice/calls/status/{provider} reconciles to actual duration cost when the call ends.
Voice API
| Method | Path | Description |
|---|---|---|
POST | /v1/voice/numbers | Link a phone number to an agent (provider + config) |
GET | /v1/voice/numbers | List linked numbers |
DELETE | /v1/voice/numbers/{id} | Unlink a number |
GET | /v1/voice/calls | Recent calls with duration and cost |
POST | /v1/voice/token | Mint a short-lived LiveKit join token |
POST | /v1/voice/webhook/{provider} | Inbound call webhook (Twilio TwiML or LiveKit JWT) |
POST | /v1/voice/calls/status/{provider} | Call-end status callback — reconciles actual cost into budget rollup. Point Twilio's “call status changes” webhook here. |
Env vars
| Variable | Purpose |
|---|---|
LANTERN_VOICE_CALLER_ID | E.164 caller-ID shown on outbound calls. Must be a Twilio verified number. |
LANTERN_VOICE_SMS_HEADSUP | on (default) — sends an SMS heads-up before the call so recipients recognize the number. |
LANTERN_VOICE_CLONE | Optional — 1 speaks outbound calls in the owner's cloned voice via ElevenLabs. Requires LANTERN_ELEVENLABS_API_KEY and LANTERN_ELEVENLABS_VOICE_ID. |
Web Chat
Embed a chat widget on any website:
<!-- widget.js is served by the dashboard app; copy the exact snippet from /embed -->
<script src="http://localhost:3001/widget.js"
data-api-key="YOUR_SCOPED_API_KEY"
data-agent="your-agent-name"
data-api-base="http://localhost:8080"
data-theme="dark"
data-position="bottom-right"
async defer></script>The widget uses the same /v1/sessions endpoints as the dashboard and streams replies over SSE. The API key is visible in the browser, so scope it to sessions and messages. The dashboard's /embed page generates the snippet.
REST API
Every agent is accessible via the REST API. See the API Reference for full details.
curl -X POST http://localhost:8080/v1/runs \
-H "Authorization: Bearer $LANTERN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"agentName": "my-agent", "input": {"topic": "quantum computing"}}'Assigning surfaces to agents
Configure a surface once for the tenant, then point it at an agent:
- Set up the surface under Channels
- On the agent's page, open its Channels tab and enable the surfaces it should answer on
Two-way communication
When a user sends a message:
- The surface gateway (or a bridge) receives the message
- It routes to the correct agent based on the surface configuration
- The agent runs with the message as input
- The response streams back through the same surface, in the same conversation or thread