Developer API
Embed a voice agent on your website
The <loomira-call> widget puts a live call button on any page with two lines of HTML. No API key ever reaches the browser: the widget mints an anonymous, short-lived session that only works from the origins you allowlist, and only while the agent is active.
Enable embedding in the portal
- Open your agent in the portal and switch to the Technical tab, section Embed.
- Enable embedding and add the exact origins your pages are served from.
- Save — the allowlist only applies once saved.
- Copy the snippet and paste it where the button should appear:
HTML
<script type="module" src="https://loomira.ai/embed/v1.js"></script>
<loomira-call agent-id="YOUR_AGENT_ID"></loomira-call> Origin rules: an entry is an exact browser origin — https://host[:port], lowercase, no path, no trailing slash, no wildcards. https://www.example.com and https://example.com are different origins; list both if both serve the page. For local development, http://localhost:PORT is allowed.
Attributes
| Attribute | Required | Description |
|---|---|---|
| agent-id | required | The agent this button calls — copied with the snippet from the portal. |
| label | optional | Idle-button text. Default: "Call us". |
| accent-color | optional | Any CSS color; shorthand for the --loomira-accent custom property. |
| api-base | optional | API origin override for testing against a non-production environment. The snippet never sets it. |
Customized
<loomira-call
agent-id="YOUR_AGENT_ID"
label="Talk to our assistant"
accent-color="#0a6c7e"
></loomira-call> Deeper theming via CSS custom properties on the element: --loomira-accent, --loomira-accent-contrast, --loomira-radius, --loomira-font.
Security model & CSP
The widget calls the anonymous POST /v1/agents/{id}/embed-token endpoint. The (agent id, Origin) pair is the credential: the browser's Origin header must match your allowlist, so the agent id being visible in your page HTML is safe — it only works from your origins. Refusals are a uniform 403 (no reason disclosed to visitors) and the endpoint is strictly rate-limited per agent and origin.
If your site ships a Content-Security-Policy, allow the widget script with script-src https://loomira.ai (plus connect-src for https://api.loomira.ai and the wss: LiveKit endpoint returned in the session, and media-src/microphone permissions as your policy requires). The script tag carries no integrity hash on purpose: the file is first-party (served from loomira.ai itself, not a third-party CDN) and evergreen — it updates with every release, so a pinned hash would break the button on the next deploy.
No-widget fallback (vanilla JS)
Prefer your own UI? Fetch the embed token yourself and join with livekit-client from npm — the same origin rules apply:
TypeScript
import { Room } from "livekit-client"; // npm install livekit-client
// Anonymous, origin-gated: works only from the https origins you allowlisted.
const session = await fetch(
"https://api.loomira.ai/v1/agents/YOUR_AGENT_ID/embed-token",
{ method: "POST" },
).then((r) => {
if (!r.ok) throw new Error("embed refused"); // 403: origin not allowed / agent not active
return r.json();
}); // -> { url, token, room }
const room = new Room();
await room.connect(session.url, session.token);
await room.localParticipant.setMicrophoneEnabled(true);
// Hang up with room.disconnect().Troubleshooting 403
- The page's origin is not on the allowlist — check protocol, host and port match exactly (and that you saved after adding it).
- Embedding is not enabled for the agent (the toggle in the Technical tab).
- The agent is not active — draft and archived agents never answer embeds.
- The request has no
Originheader — server-to-server calls must use the API-key-authenticated/web-callmint instead. - All other refusals (e.g. workspace billing state) are folded into the same uniform 403 on purpose; check the portal for the actual state.