Live on your site in two lines
Copy-paste snippets for every stack, the full init reference, and a REST API for querying and indexing programmatically.

Quick start
Three things stand between an empty bot and a working one. Most sites are answering questions the same afternoon.
- 1
Add your content. Point the crawler at your site in the Knowledge tab. Everything the assistant will ever say comes from what you index here, so it is worth reviewing the source list once the first crawl finishes.
- 2
Paste the snippet. Two lines before the closing body tag. Nothing to install, no package, no build step.
- 3
Lock it down. Add your domains to the allow-list and set a daily cap before you publish. Both take a minute and neither can be added retrospectively to a bill.
Install the widget
Two lines before </body>, on any site. It loads after your page finishes, so it never blocks rendering. On a builder, CMS or framework, pick it below for the exact steps — several connect themselves rather than asking you to paste anything.
<!-- Matter Chat — paste right before </body> -->
<script>
window.MatterChat = window.MatterChat || { q: [] };
window.MatterChat.q.push(["init", { id: "YOUR_BOT_KEY" }]);
</script>
<script async src="https://app.matterchat.co/widget/7e4105892a01.js"
integrity="sha384-Ad9fQNB21lekRAm92bhT3xTEgdh8/x6L0usYOaBWYEB/S+cSy6r+EvgUepDSnjR0"
crossorigin="anonymous"></script>Choose your platform
Any site
Site builders
CMS
Ecommerce
Frameworks
Docs sites
The snippet pushes onto a queue rather than calling a function directly, which is why the order of the two script tags does not matter and why nothing breaks if the widget script is still loading when your page finishes.
Init options
Appearance is normally managed in the dashboard — no code changes needed. Inline options win when both are set, which makes them useful for a per-site override without touching the bot's configuration.
| Option | Type | What it does |
|---|---|---|
| idrequired | string | Your bot key from the dashboard Install tab. Starts with bot_ and is safe to expose — it is bounded by your domain allow-list, not by being secret. |
| title | string | Overrides the header title set in the dashboard. |
| greeting | string | Overrides the first message the visitor sees. Naming what the assistant covers here produces better questions than an open invitation does. |
| accent | string | Hex colour overriding the dashboard accent, e.g. "#0F9D6B". |
| position | "right" | "left" | Which corner the launcher sits in. Defaults to "right". |
All options together
window.MatterChat = window.MatterChat || { q: [] };
window.MatterChat.q.push(["init", {
id: "YOUR_BOT_KEY",
title: "Acme Support",
greeting: "Ask about shipping, returns, or sizing.",
accent: "#0F9D6B",
position: "right",
}]);How the assistant behaves
Worth knowing before you launch, because two of these surprise people who expect a general-purpose chatbot.
It answers only from your content.
Not from general knowledge. A question your indexed material does not cover gets a plain refusal rather than a plausible guess — which is the behaviour you want on a refund policy.
Every answer cites its source.
The reply links the page it drew from, so a visitor can verify it. If an answer is wrong, the cited page is usually the reason.
Refusals are recorded.
Each one is logged and grouped by demand, which is where the content-gap report comes from. A refusal is a signal, not a failure.
It offers a human when it cannot help.
Depending on your configuration and whether your team is marked available, it hands over to live chat or takes a contact detail.
Conversations persist for the visitor.
Someone returning to the page can continue where they left off, and clear their own history from the widget.
It answers in the language asked.
Drawn from the same content you maintain in one language — there is no per-language setup.
Before you go live
Four checks that keep the bot accurate and your spend bounded.
- 1
Set the domain allow-list. Add every domain the widget runs on — subdomains count separately, so acme.com and www.acme.com are two entries. An empty list means any site can use your bot key.
- 2
Set the daily reply cap. How many answers the bot may give in a day. When it is reached the widget pauses politely rather than continuing to spend. Set it above a normal day and below what you would be unhappy to pay on the worst one.
- 3
Review what was actually crawled. Open the source list. You are looking for important pages that are missing, and noise that should not be there — tag archives, pagination, or an old staging domain.
- 4
Ask it ten hard questions. Use the dashboard test chat, and check the citation on each answer rather than only the answer. A correct answer citing an unrelated page means retrieval got lucky and the next question will not.
REST API
Create an API key under Account → API keys, then send it as Authorization: Bearer mc_sk_…. Your bot ids appear in each bot's dashboard URL. API access is an Agency plan feature.
Ask a question — POST /api/v1/query
curl -X POST https://app.matterchat.co/api/v1/query \
-H "Authorization: Bearer mc_sk_..." \
-H "Content-Type: application/json" \
-d '{"botId": "YOUR_BOT_ID", "question": "Do you ship internationally?"}'Response
{
"answer": "Yes — we ship to most of Europe and North America...",
"sources": [
{ "n": 1, "title": "Shipping & delivery", "url": "https://acme.com/shipping" }
],
"grounded": true
}grounded: false means nothing relevant was found. When that happens answer is null and sources is empty — no answer is invented to fill the gap. Treat it as a refusal and route it the way you would route one in the widget.
Add or refresh a page — POST /api/v1/ingest
curl -X POST https://app.matterchat.co/api/v1/ingest \
-H "Authorization: Bearer mc_sk_..." \
-H "Content-Type: application/json" \
-d '{"botId": "YOUR_BOT_ID", "url": "https://your-site.com/new-page"}'Response
{
"sourceId": "src_...",
"chunkCount": 12,
"skipped": false,
"error": null
}skipped: true means the page content has not changed since the last ingest, so nothing was re-indexed. Calling this after a deploy is safe and cheap — unchanged pages cost nothing.
Status codes
Every error response is JSON with an error string. The message distinguishes the four different reasons for a 429, which is the one you are most likely to handle.
| Code | What happened | What to do |
|---|---|---|
| 400 | The body was not valid JSON, or a required field was missing or malformed. | Check botId and question are present, and that url on ingest is a full http(s) address. |
| 401 | The Authorization header was missing, malformed, or the key is not valid. | Send Authorization: Bearer mc_sk_… with a key from Account → API keys. |
| 403 | The workspace's plan does not include API access. | API access is an Agency plan feature. Checked per request, so a plan change takes effect immediately. |
| 404 | No active bot with that id belongs to your workspace. | Confirm the bot id and that the bot is active. Ids are scoped to your workspace, so another workspace's id reads as unknown. |
| 429 | A rate limit, daily cap, or monthly allowance was reached. The message says which. | See Rate limits below — each of the four has a different remedy. |
| 500 | The request was valid but could not be completed. | Safe to retry once. If it persists, get in touch rather than retrying in a loop. |
A 429 is not always a rate limit. It is also returned when the bot has hit its daily cap, its daily message limit, or the workspace's monthly allowance — so back off and retry only when the message says the rate limit. The other three will not clear in a few seconds.
Rate limits and quotas
Limits are scoped to the workspace rather than to the individual key, so minting more keys does not raise your ceiling.
| What | Limit | Scope |
|---|---|---|
| POST /api/v1/query | 30 per minute | Per workspace |
| POST /api/v1/ingest | 60 per hour | Per workspace |
| Question length | 2,000 characters | Per request — longer questions are truncated, not rejected |
| Query response time | 60 seconds | Per request |
| Ingest response time | 120 seconds | Per request — a large page takes longer |
Widget traffic is bounded separately, by your per-visitor rate limit and the bot's daily reply cap. Those are set per bot in the dashboard rather than here, and they apply whether or not you use the API.
Keys and domain locks
There are two kinds of key and they are protected in completely different ways. Treating them alike is the mistake worth avoiding.
Bot key — bot_…
Public by necessity: it sits in your page source so the widget can identify itself. It is not a secret and rotating it is not the control. What protects it is the domain allow-list — on an origin you have not listed, it simply will not answer.
API key — mc_sk_…
A genuine secret. Server-side only — never in client JavaScript, a mobile app, or a public repository. Anyone holding it can query and ingest against your workspace. If one leaks, revoke it in the dashboard rather than trying to contain it.
Subdomains are separate origins. acme.com and www.acme.com are two allow-list entries, and forgetting the second is the most common reason a widget works locally and not in production.
Troubleshooting
The four things that account for nearly every support message about a fresh install.
The widget does not appear.
Check three things in order: that you are looking at the published site rather than an editor preview, since several platforms do not run custom code in preview; that the current domain is on the allow-list, including the www variant; and that the snippet landed before the closing body tag rather than somewhere the platform strips scripts.
It refuses things it should know.
Almost always a coverage problem rather than a tuning one. Open the source list and confirm the page that answers the question was actually crawled — pages linked from nowhere are never discovered. If it is missing, add it directly; if it is not written yet, the gap report will keep reminding you.
An answer is wrong but confident.
Read the citation. A wrong answer with a citation nearly always means the cited page is out of date — an old pricing or policy page that is still indexed. Stale content is worse than missing content, because it produces authoritative answers with a source attached.
The API returns 403.
API access is an Agency plan feature and is checked on every request rather than when the key is created. A key that worked yesterday and 403s today usually means the plan changed, not the key.
Answer honestly. Capture the rest.
Point Matter Chat at your site and see what it can — and can't — answer. It's honest about both.
No credit card. 2 minute setup.
Every answer cites the source it came from. When there isn't one, it says so — and hands the visitor to you.
Installs on the tools you already run.