On this page
Overview
Getting startedGetting started
Miko puts your web chat and email in one inbox. Miko AI answers from your knowledge base and hands the conversation to your team when it isn't sure.
- Create a workspace from the sign-up page.
- Add knowledge: your website, help articles or documents (Knowledge).
- Put the web chat on your site (Settings › Channels › Web chat).
- Connect your support address (Settings › Channels › Email).
You can also ask Operator to do the setup: write what you want, for example “Index my site and test Miko AI with return questions”, and it runs the steps live.
Install the web chat
The install tag is on Settings › Channels › Web chat › Install, already filled in with your workspace key. Paste it before the closing </body> tag on every page where the chat should appear.
<script src="https://getmiko.co/widget.js" data-key="YOUR_WORKSPACE_KEY" async></script>In a single-page app, load the same script from code once the app starts:
const script = document.createElement("script");
script.src = "https://getmiko.co/widget.js";
script.dataset.key = "YOUR_WORKSPACE_KEY";
script.async = true;
document.body.appendChild(script);Allowed sites: if you list your domains in the web chat settings, the chat opens only on those sites. With the list empty it opens anywhere the tag is placed.
Identify signed-in users
When a visitor is signed in on your site, pass their id and details before the tag. Their conversations then follow them across devices and land on the same contact.
<script>
window.mikoSettings = {
userId: "u_123",
userHash: "<computed on your server>",
email: "[email protected]",
name: "Ada Lovelace",
company: { id: "c_42", name: "Acme", plan: "Pro" },
attributes: { orders: 12 }
};
</script>
<script src="https://getmiko.co/widget.js" data-key="YOUR_WORKSPACE_KEY" async></script>Only the id is trusted, and only with a matching userHash: HMAC-SHA256 of the user id under your identity secret (Settings › Channels › Web chat › Security). Compute it on your server; never put the secret in the page. A missing or wrong hash is refused.
import { createHmac } from "node:crypto";
// On your server, never in the page.
const userHash = createHmac("sha256", process.env.MIKO_IDENTITY_SECRET)
.update(user.id)
.digest("hex");On sign-out, end the session so the next person on the device starts fresh:
Miko("shutdown");JavaScript API
Once the tag has loaded, the page can drive the chat with window.Miko:
Miko("open")- Open the chat.
Miko("close")- Close it.
Miko("toggle")- Open it if closed, close it if open.
Miko("hide")- Remove the launcher, for a site that opens the chat from its own button.
Miko("show")- Bring the launcher back.
Miko("newMessage", "…")- Open the chat with a message already typed (up to 2,000 characters).
Miko("identify", { userId, userHash, … })- Identify a user after the page has loaded, for example right after sign-in.
Miko("shutdown")- Forget the signed-in user.
Miko("on", "unread", fn)- Listen for open, close and unread.
Miko("off", "unread", fn)- Stop listening.
Connect email
Settings › Channels › Email offers two ways in:
- Forward your support address to the inbox address Miko gives your workspace. Every mail becomes a conversation; customers keep writing to your own address.
- Or connect the mailbox itself over IMAP and SMTP (Gmail, Outlook or your own server). Miko reads new mail and sends replies through it.
To send replies from your own domain, add the DKIM and return-path records shown under Domains & addresses; the screen checks them for you. DMARC tells mailbox providers what to do with mail that isn't signed.
Knowledge
Miko AI answers only from what you add here, and every answer shows the article or page it used.
- Website: pages are crawled and kept in sync; each sync is kept in a history.
- Help centre articles: published on your help centre and used by Miko AI.
- Internal articles: used by Miko AI and Cue, never shown to customers.
- Snippets: short facts and answers that don't need an article.
- Documents: files you upload, such as a returns policy.
Miko AI and handoff
Miko AI replies in the language of the conversation. When your knowledge has no answer, or your AI credit runs out, it hands the conversation to a teammate who is available right then, with the whole history attached.
Inside the inbox, Cue drafts a reply for the agent from the same knowledge. The agent edits it and sends it; nothing Cue writes reaches a customer by itself.
REST API
Create a key under Settings › Integrations › Developers. Keys start with mk_live_ and act as the teammate who created them: they see and do what that teammate may. Send the key in the Authorization header:
curl https://getmiko.co/api/dev/v1/conversations?status=open \
-H "Authorization: Bearer mk_live_…"Endpoints, all under https://getmiko.co/api/dev/v1:
- GET
/conversations - Conversations, newest activity first.Query: status, assigneeId, search, limit (up to 100), cursor (pageInfo.nextCursor from the previous page).
- GET
/conversations/{id} - One conversation with its messages.
- POST
/conversations/{id}/replies - Reply to the customer. Body: { "body": "…" }.
- POST
/conversations/{id}/notes - Add an internal note. Body: { "body": "…" }.
- POST
/conversations/{id}/status - Move the conversation. Body: { "action": "resolve" | "reopen" | "snooze" | "close", "hours": 4 } — hours for snooze only.
- POST
/procedures/{id}/runs - Start a procedure in a conversation; the procedure must allow it. Body: { "conversationId": "…", "vars": { … } }.
- POST
/signals - A signal for one customer from your server (a payment failed at the provider). Body: { "name": "payment_failed", "customer": { "userId": "u_123" } or { "email": "…" }, "data": { … } }. A procedure set to the signal opens a conversation.
The page can send the same signal itself, from the visitor's browser. A procedure set to it opens the chat with its first words; at most one conversation per visit and three a day per person, and none while they are talking with your team.
<script>
// On the page itself, after the chat's snippet:
Miko("signal", "payment_failed", { amount: 349.9, reason: "card_declined" });
</script>Errors come back as JSON with a code and a message:
{ "error": { "code": "…", "message": "…", "requestId": "…" } }MCP server
Miko is an MCP server at https://getmiko.co/mcp (Streamable HTTP). Connect Claude Code, Cursor or your own agent with a developer API key; it works as the teammate who created the key, within the key's scope and that teammate's role, and every tool call is written to the audit log. In Claude Code:
claude mcp add --transport http miko https://getmiko.co/mcp \
--header "Authorization: Bearer mk_live_…"In Cursor, in .cursor/mcp.json:
{
"mcpServers": {
"miko": {
"url": "https://getmiko.co/mcp",
"headers": { "Authorization": "Bearer mk_live_…" }
}
}
}A key's scope decides which tools it may call. A read key reads; a write key also replies to customers — a reply reaches the customer, so assistants are told to confirm it first — writes notes, assigns, closes and creates; an admin key also merges contacts.
read- search_conversations, get_conversation, search_customers, get_customer, search_knowledge, list_tickets, get_report, list_procedures
write- reply_to_conversation, add_note, assign_conversation, close_conversation, create_ticket, create_snippet, draft_article, start_procedure
admin- merge_customers
Every tool is also a REST call, with its arguments as the body:
curl -X POST https://getmiko.co/api/dev/v1/tools/get_report \
-H "Authorization: Bearer mk_live_…" -H "Content-Type: application/json" \
-d '{"days": 7}'Webhooks
Add a webhook under Settings › Integrations › Developers: an https address and the events it should receive. The test button sends a webhook.test delivery.
Events:
conversation.created- A new conversation started.
message.created- The customer wrote.
message.replied- A teammate replied.
message.ai_replied- Miko AI replied.
conversation.handed_off- Miko AI handed the conversation to your team.
conversation.status_changed- The conversation was resolved, reopened, snoozed or closed.
conversation.assigned- The conversation was assigned.
conversation.rated- The customer rated the conversation.
customer.merged- Two contacts were merged into one.
order.action- An order cancellation or refund was prepared, approved, declined or done.
A delivery carries ids and the facts of the event, not the whole conversation; read the rest with the API.
POST /your/webhook
Miko-Signature: t=1790290000,v1=9f3c…
{
"id": "0b8e…",
"type": "conversation.handed_off",
"createdAt": "2026-09-25T09:41:00.000Z",
"workspaceId": "5d21…",
"data": { "conversationId": "4c62…" }
}Every delivery is signed. Recompute the signature from the raw body and refuse old timestamps, which stops a captured delivery being replayed:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
const age = Date.now() / 1000 - Number(parts.t);
if (!(age < 300)) return false; // older than five minutes
const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const given = Buffer.from(parts.v1 ?? "");
return given.length === expected.length && timingSafeEqual(given, Buffer.from(expected));
}Answer with any 2xx within 10 seconds. Anything else is retried with a growing delay, up to six hours apart.