Headless SDK
Same public key as the widget, same knowledge, same plan — without the launcher. Put an input on a help page, a docs site or inside your app, and render the answer however you like. Conversations land in your inbox next to widget chats, so ratings, leads and human handoff keep working. The page's domain must be in the bot's Allowed domains, exactly as for the widget.
No build step — one tag
<script async src="https://app.matterchat.co/askbox.js"
data-key="YOUR_BOT_KEY"
data-suggestions="Pricing|Refunds|Shipping"></script>Renders a search-style ask box where the tag sits, in its own shadow root, so your theme and it leave each other alone. Works on Docusaurus, Mintlify, GitBook, Notion-exported sites, WordPress — anywhere you can paste HTML. Options: data-target="#selector" to render elsewhere, data-placeholder, data-accent="#hex", data-unbranded. Pin it with /askbox/<hash>.js from the build if you want Subresource Integrity.
Or a package, for your own UI
pnpm add @matterchat/core # framework-free
pnpm add @matterchat/react # or vue, svelte, angular — each exports useMatterChatOne question, one streamed answer
import { createMatterChat } from "@matterchat/core";
const mc = createMatterChat({ publicKey: "pk_live_…" });
const answer = await mc.ask("Do you ship to Canada?", {
onToken: (_chunk, full) => (output.textContent = full),
});
answer.sources; // [{ n: 1, title: "Shipping", url: "https://…" }]A help page in React
import { useMatterChat } from "@matterchat/react";
export function AskBox() {
const { messages, ask, streaming, error } = useMatterChat({ publicKey: "pk_live_…" });
return (
<form onSubmit={(e) => { e.preventDefault(); ask(e.currentTarget.q.value); e.currentTarget.reset(); }}>
<input name="q" placeholder="Ask anything about our product" disabled={streaming} />
{messages.map((m, i) => (
<p key={i} data-role={m.role}>
{m.content}
{m.sources?.map((s) => <a key={s.n} href={s.url ?? "#"}>[{s.n}]</a>)}
</p>
))}
{error && <p role="alert">{error.message}</p>}
</form>
);
}useMatterChat keeps one conversation: the question appears at once, the answer grows token by token, and follow-ups remember what was asked. rate(1 | -1) rates the latest answer; reset() starts over. Vue, Svelte and Angular expose the same shape as a composable, a store and signals.
What an answer tells you
text | The answer, as Markdown — render it with the same renderer as the rest of your page. Streams through onToken first. |
sources | Pages the answer came from, in citation order. |
refusal | Nothing relevant was found; the bot said so instead of guessing. |
lowConfidence | An answer from thin evidence — a good moment for “was this helpful?”. |
handoff | The visitor asked for a person and your team was paged. |
humanMode | A teammate owns this conversation; their reply arrives in the inbox, text is empty. |
ask() throws a MatterChatError with a code: rate_limited, capped (plan limit), not_allowed (domain not listed), insecure_context (the page is plain HTTP — attestation needs HTTPS or localhost), unavailable, network. The hooks catch these into errorand keep the visitor's question on screen.