Use this file to discover all available pages before exploring further.
Embed your agent as a floating chat bubble on any website. Visitors open the chat, ask questions, and receive streamed AI responses with markdown and images — without leaving your page or needing an Abundly account. Widgets are typically embedded on pages that require login — a customer portal, internal dashboard, or help center — so that only authenticated users can chat. However, you can also use the widget on public-facing pages. A separate daily credit limit can be configured for the widget to control usage independently of the agent’s main limit. Every incoming widget message is also screened by built-in attack detection, which blocks prompt-injection and data-exfiltration attempts before a response reaches the visitor. Widget conversations are stored as full platform chats — they appear in the portal chat list alongside regular and Slack conversations, with complete message history and admin tooling.
Want to see it in action? The live widget showcase has two interactive demos — the ready-made bubble and a fully custom UI built with createClient.The widget has two modes:
Ready-made widget (mountChatWidget) — a complete floating chat bubble and panel with streaming responses, a processing indicator (animated dots while the agent works), markdown rendering, and image support. One function call and you’re done.
Custom UI (createClient) — a headless client that handles backend communication, streaming, and markdown rendering while you build the UI entirely yourself.
If you’re using plain HTML, PHP, WordPress, or any non-React stack, you only need a <script> tag pointing at the Abundly-hosted loader — no npm install or build step required. If you’re building a custom UI or working in a React/Next.js project, install the @abundly/widget npm package instead. Both approaches share the same backend proxy contract.
The chat widget is gated behind a workspace feature flag. Contact support@abundly.ai to have it enabled for your workspace.
The widget never talks to Abundly directly. A server-side proxy on your backend holds the API key and forwards requests to Abundly server-to-server:
Browser --> Your backend proxy --server-to-server--> Abundly API (holds API key) X-Agent-Access-Key
This keeps the API key out of client-side code. If the widget should only be available to logged-in users, protect the proxy with your existing authentication.The widget calls three routes on your backend:
Route
Method
Forwards to
/status
GET
…/widget/status
/chat
POST
…/widget/chat (SSE stream)
/images/<path>
GET
…/widget/images/<path>
All upstream requests use header X-Agent-Access-Key: {API_KEY}. The full upstream URL pattern is {SERVICE_URL}/agents/{AGENT_ID}/widget/{route}.CORS: the widget runs in the visitor’s browser. If backendUrl points to a different origin than the page, your proxy must allow cross-origin requests. A same-origin route (e.g. /api/abundly on the same host) avoids this entirely.
In the Abundly portal, open your agent’s Settings → API Access tab. Turn on Enable widget and choose which capabilities the widget may use. Then create an API access key (starts with ak_) in the API Keys section — you’ll store this on your server only.You can also configure:
Daily credit limit — a separate cap for widget usage, independent of the agent’s main limit
System prompt override — extra instructions for widget conversations only
Demo response override — a fixed reply for presentations (skips the LLM)
Model override — run widget conversations on a specific model
Depending on your setup, widget users may range from logged-in employees on an internal dashboard to anonymous visitors on a public page. The primary defense is the capability list on the widget itself — only enable what the widget actually needs. As an extra layer against misconfiguration, it’s a good idea to use restrictive settings on the agent’s capabilities in general. For example, setting Update Instructions to chat only ensures it is automatically blocked in widget conversations, even if someone accidentally adds it to the widget’s capability list.
2
Implement the backend proxy
Create a server-side endpoint that the widget can call. It must expose /status, /chat, and /images/<path> under a single base URL. Each route forwards the request to Abundly with your API key.You need three environment variables (values shown in the portal):
Variable
Description
ABUNDLY_SERVICE_URL
Abundly service endpoint
ABUNDLY_AGENT_ID
Your agent’s ID
ABUNDLY_API_KEY
API access key (ak_…)
See backend proxy examples below for ready-to-use code in Node.js, Express, Next.js, and PHP.
3
Embed the widget
Add the widget script to your page and mount it, pointing at your backend:
<script src="https://app.abundly.ai/widget.js"></script><script> Abundly.mountChatWidget({ backendUrl: "/api/abundly", textOptions: { bubbleLabel: "Chat with us", welcomeMessage: "Hello! How can I help you today?", }, });</script>
The loader injects the current versioned widget bundle automatically — your snippet stays the same across releases.For React / Next.js, install the npm package instead:
Open http://localhost:3000 (port 8080 for PHP). You should see the chat bubble in the bottom-right corner. Click it, type a message, and see the streamed response from your agent.
These examples are minimal — they forward requests to Abundly but do not authenticate the browser caller. In production, add your own auth middleware (session cookies, JWT verification, etc.) to protect these endpoints.
mountChatWidget takes care of these concerns automatically:
Availability check — calls /status before showing the bubble; stays hidden if the agent is unavailable or the daily limit is exhausted
SSE streaming — character-by-character reveal animation as the response arrives
Processing indicator — animated dots while the agent is working (tools, sub-agents, or waiting for a response). Internal reasoning text is never shown in the widget.
Markdown rendering — safe HTML output with XSS protection
Image support — URL rewriting, preloading, and authenticated image loading through the proxy
Mobile layout — soft-keyboard detection and layout adjustment
Auto-resizing input — textarea grows up to three lines
Rate limiting — displays errors and suspends input on HTTP 429
mountChatWidget returns a handle with two methods:
Method
Description
handle.reset()
Clears conversation history, errors, and suspended state. Restores the welcome message if configured.
handle.unmount()
Removes bubble, panel, backdrop, and injected styles. Idempotent, safe to call at any time.
If you need full control over the chat interface — your own layout, animations, or interaction model — use createClient instead of mountChatWidget. It provides the same streaming and protocol engine without any DOM or styles.
import { createClient } from "@abundly/widget";const client = createClient("/api/abundly");
client.checkStatus() — check if the agent is available
client.sendMessage(text, callbacks) — stream a response via SSE with onChunk, onProcessing, onDone, and onStreamError callbacks
client.resetSession() — clear the server-side session so the next message starts a new conversation
client.renderMarkdown(text) — convert markdown to safe HTML (optional — use your own library instead if you prefer)
client.stripPartialMd(text) — clean up half-formed markdown tokens during streaming
Conversation history is persisted server-side — you only need a local Message[] for rendering your UI. The client handles protocol details, session management, URL rewriting, and optionally markdown rendering. You own all markup and styling.
The methods listed above are the most commonly used — the full client API includes additional helpers for image authentication, blob URL management, and more. See the @abundly/widget package on npm for the complete reference.
The usual flow for a custom UI:
Backend first — same proxy as above (/status, /chat, /images/<path>)
Install the SDK — npm install @abundly/widget
Create a client — const client = createClient("/api/abundly")
Build your component — use client.sendMessage for streaming, client.renderMarkdown for replies, client.checkStatus for availability. Authenticated proxies and inline images require additional client helpers — see the npm package docs for details
Ship it — mount wherever it should live; no mountChatWidget required
Minimal React example
"use client";import { useState } from "react";import { createClient, type Message } from "@abundly/widget";const client = createClient("/api/abundly");export function MyChat() { const [messages, setMessages] = useState<Message[]>([]); const [streaming, setStreaming] = useState(""); const [draft, setDraft] = useState(""); async function send() { const text = draft.trim(); if (!text) return; setDraft(""); setMessages((prev) => [...prev, { role: "user", content: text }]); setStreaming(""); // No need to pass conversation history — the server tracks it // via a session ID that the client manages automatically. const final = await client.sendMessage(text, { onChunk: (accumulated) => setStreaming(accumulated), }); setStreaming(""); setMessages((prev) => [...prev, { role: "assistant", content: final }]); } function resetChat() { client.resetSession(); setMessages([]); } return ( <div> {messages.map((m, i) => ( <div key={i} className={m.role}> {m.role === "user" ? ( m.content ) : ( <div dangerouslySetInnerHTML={{ __html: client.renderMarkdown(m.content), }} /> )} </div> ))} {streaming && ( <div className="assistant"> <div dangerouslySetInnerHTML={{ __html: client.renderMarkdown(client.stripPartialMd(streaming)), }} /> </div> )} <input value={draft} onChange={(e) => setDraft(e.target.value)} /> <button onClick={send}>Send</button> <button onClick={resetChat}>New chat</button> </div> );}
For the full API reference, implementation help, TypeScript types, image auth helpers, and markdown renderer comparison, see @abundly/widget on npm.
If your proxy is same-origin and uses session cookies, no extra configuration is needed — cookies flow automatically.For token-based or cross-origin setups, pass headers and/or credentials on the mount call. Both mountChatWidget and createClient accept the same options.
Bearer / JWT
Static header
Cross-origin cookies
Use the function form so the token is re-read on every request:
headers is applied to every request the widget makes (/status, /chat, and image loads). User-supplied headers override built-in headers with the same name. credentials maps directly to the fetch option.