# API keys and plans (/api-keys-and-plans) ## API keys [#api-keys] An API key lets an AI client reach your workspace through Sondo's [MCP server](/mcp). Each client gets its own key, and you can revoke any of them at any time without touching the others. Keys live on the Integrations page in the dashboard, alongside the endpoint every client connects to. The one endpoint every client connects to is `https://usesondo.com/api/mcp`. ### Issuing a key [#issuing-a-key] Click **Create API key** and give it a label. Name it after the client you are connecting, for example "Claude Code on my laptop", because the label is the only way you will tell your keys apart later. The key is then shown **once**. Copy it before you close the dialog: Sondo stores only a hash of it, so a lost key can be re-issued but never recovered. The dialog deliberately will not close on Escape or a stray click while the key is on screen. Every key starts with `sondo_sk_`. Treat it like a password. It grants access to the conversations in that workspace. You can hold up to twenty active keys per workspace. Revoked keys do not count. ### Revoking a key [#revoking-a-key] **Revoke** on any row. Any client using that key stops working immediately. A revoked key stays in your list as a record and cannot be un-revoked. To reconnect that client, issue a new key. The table also shows when each key was created and when it was last used, so an unused key is easy to spot before you revoke it. ## Plans [#plans] Every feature is on every plan. They differ only in how much they hold. | Plan | Monthly | Yearly | Workspaces | Knowledge Base | | ---- | ----------- | ----------- | ---------- | -------------- | | Solo | $12 a month | $120 a year | 1 | 5,000 pages | | Pro | $29 a month | $290 a year | 5 | 15,000 pages | A page is 2,000 characters of text, roughly one printed page. Prices are in United States dollars and exclude tax. Paying yearly costs ten months rather than twelve. If you need more than Pro holds, there is a Pro Max tier with unlimited workspaces and an unlimited Knowledge Base, arranged by email rather than bought online. The [pricing page](https://usesondo.com/pricing) has the address to write to. There is no trial and no free tier: your widget answers visitors while the subscription is active. If a payment fails, Sondo keeps answering while the card is retried rather than cutting your site off over a card that expired. You can change plans or cancel at any time from your account page in the dashboard. Everything you added stays in place if you cancel, and comes back with the subscription. # CLI (/cli) `sondo` does from a terminal what the dashboard does in a browser: read the inbox, reply to a visitor, load the Knowledge Base, change settings, print the embed snippet. ```bash npm install -g @usesondo/cli # or, without installing it npx @usesondo/cli status ``` It needs Node 20 or newer. ## Sign in [#sign-in] Issue an API key on the Integrations page in your dashboard, then: ```bash sondo login --key sondo_sk_… ``` The key is saved under a profile named after the workspace it opens, in `~/.config/sondo/config.json`, readable only by you. A key opens exactly one workspace. Run `sondo login` once per workspace, then switch with `sondo workspace use `, or aim a single command somewhere else with `--workspace `. For continuous integration, set `SONDO_API_KEY` and `SONDO_URL` instead. They override the saved profiles, so nothing has to be signed in on the machine. ## Three things to try [#three-things-to-try] ```bash # what came in while you were away sondo inbox --unread --detail summaries # load a folder of notes, a PDF and a page into the Knowledge Base sondo kb add ./docs ./handbook.pdf https://example.com/pricing # reply to a visitor sondo conversation reply "Thanks, I have sent you a booking link." ``` ## Flags every command takes [#flags-every-command-takes] | Flag | What it does | | -------------------- | -------------------------------------------------------------------- | | `--json` | Print the answer exactly as Sondo returned it, for piping into `jq`. | | `--workspace ` | Run this one command against another saved workspace. | | `--profile ` | The same thing, under its other name. | Errors go to standard error and the command exits 1, so a failure is visible to a script and not just to a reader. ## Adding to the Knowledge Base [#adding-to-the-knowledge-base] `sondo kb add` takes files, folders and addresses in one go, and decides what each one is. A Markdown, text or HTML file is sent as text. Its title is its frontmatter `title` if it has one, then its first `#` heading, and otherwise its filename. A PDF is uploaded. An `http` or `https` address is fetched by Sondo rather than by your machine. A folder is walked for those file types, skipping dotfiles and `node_modules`. Every source gets its own line of output. One failure does not stop the rest, and the command exits 1 if anything failed at all. A file over 2 MB, or a text file over 200,000 characters, is skipped rather than truncated, because a silently half-loaded document is worse than one you were told about. With `--json` you get a single array of per-source outcomes instead of the lines. ## Changing settings [#changing-settings] `settings set` and `widget set` take `key=value` pairs. Each value is parsed as JSON, and falls back to the raw string when that fails. So `starters='["Pricing?"]'` is an array and `showHeader=false` is a boolean, while `headerTitle=Ask us` is the string you typed. Mind the ones that look like text: `headerTitle=true` is the boolean, not the word. ```bash sondo settings set timezone=Europe/Amsterdam resumeWindowHours=24 sondo widget set headerTitle="Ask us" starters='["Pricing?","Book a call"]' ``` One rejected key fails the whole command, so a half-applied change cannot happen. ## How it works [#how-it-works] The CLI speaks nothing but Sondo's [MCP endpoint](/mcp), calling the same tools an AI client would, with your API key as a bearer token. That is the point of it: anything the CLI can do, an agent can do, and the reverse. Most commands are one call. A few are not. `logout`, `workspace list` and `workspace use` only touch the config file on your machine and reach Sondo not at all. `kb add` makes one call per source, which is why one failing source does not stop the others. `widget snippet` makes the same call as `widget get` and prints one field of the answer. ## Command reference [#command-reference] Generated from the tool itself. `sondo --help`, and `sondo --help`, list the commands in your terminal. The CLI has 37 commands. ### Signing in [#signing-in] One key opens one workspace, so sign in once per workspace and switch between them. #### `sondo login` [#sondo-login] Save an API key as a workspace profile. The profile is named after the workspace. | Argument or flag | What it does | | ---------------- | --------------------------------------------------------------------------- | | `--key ` | The sondo*sk* key. Prompted for when omitted. | | `--url ` | Where Sondo runs. Defaults to [https://usesondo.com](https://usesondo.com). | ```bash sondo login --key sondo_sk_... ``` #### `sondo logout [slug]` [#sondo-logout-slug] Forget a saved workspace. Defaults to the current one. | Argument or flag | What it does | | ----------------- | -------------------- | | `slug` (optional) | Workspace to forget. | ```bash sondo logout mahdif ``` #### `sondo workspace list` [#sondo-workspace-list] The saved workspaces, with the default marked. ```bash sondo workspace list ``` #### `sondo workspace use ` [#sondo-workspace-use-slug] Make a saved workspace the default for later commands. | Argument or flag | What it does | | ---------------- | ----------------------- | | `slug` | Workspace to switch to. | ```bash sondo workspace use mahdif ``` #### `sondo status` [#sondo-status] Check the connection and report which workspace the key opens. ```bash sondo status ``` ### Conversations [#conversations] Read the inbox, act on a thread, and take over from the assistant when you want to. #### `sondo inbox` [#sondo-inbox] Conversations, most recently active first. | Argument or flag | What it does | | -------------------- | ------------------------------------------- | | `--unread` | Only unread conversations. | | `--archived` | Only archived conversations. | | `-q, --query ` | Search titles, messages and visitor emails. | | `--detail ` | titles (default) or summaries. | | `--page ` | Page number, from 1. | ```bash sondo inbox --unread --detail summaries ``` #### `sondo unread` [#sondo-unread] How many conversations are unread. ```bash sondo unread ``` #### `sondo conversation get ` [#sondo-conversation-get-id] One conversation in full: transcript, visitor context and cost. | Argument or flag | What it does | | ---------------- | ------------------------------------ | | `id` | Conversation id, from `sondo inbox`. | ```bash sondo conversation get 0f5c... ``` #### `sondo conversation read ` [#sondo-conversation-read-id] Mark a conversation read, or pass all to clear the inbox. | Argument or flag | What it does | | ---------------- | ------------------------ | | `id` | Conversation id, or all. | ```bash sondo conversation read all ``` #### `sondo conversation unread ` [#sondo-conversation-unread-id] Mark one conversation unread. | Argument or flag | What it does | | ---------------- | ---------------- | | `id` | Conversation id. | ```bash sondo conversation unread 0f5c... ``` #### `sondo conversation archive ` [#sondo-conversation-archive-id] Move a conversation out of the inbox. Reversible, and it deletes nothing. | Argument or flag | What it does | | ---------------- | ---------------- | | `id` | Conversation id. | ```bash sondo conversation archive 0f5c... ``` #### `sondo conversation restore ` [#sondo-conversation-restore-id] Bring an archived conversation back to the inbox. | Argument or flag | What it does | | ---------------- | ---------------- | | `id` | Conversation id. | ```bash sondo conversation restore 0f5c... ``` #### `sondo conversation star ` [#sondo-conversation-star-id] Star a conversation. | Argument or flag | What it does | | ---------------- | ------------------------------------ | | `id` | Conversation id, from `sondo inbox`. | ```bash sondo conversation star 0f5c... ``` #### `sondo conversation unstar ` [#sondo-conversation-unstar-id] Remove the star from a conversation. | Argument or flag | What it does | | ---------------- | ------------------------------------ | | `id` | Conversation id, from `sondo inbox`. | ```bash sondo conversation unstar 0f5c... ``` #### `sondo conversation pause ` [#sondo-conversation-pause-id] Stop the assistant from answering in this conversation. | Argument or flag | What it does | | ---------------- | ------------------------------------ | | `id` | Conversation id, from `sondo inbox`. | ```bash sondo conversation pause 0f5c... ``` #### `sondo conversation resume ` [#sondo-conversation-resume-id] Let the assistant answer again. | Argument or flag | What it does | | ---------------- | ------------------------------------ | | `id` | Conversation id, from `sondo inbox`. | ```bash sondo conversation resume 0f5c... ``` #### `sondo conversation block ` [#sondo-conversation-block-id] Block this visitor from sending more messages. | Argument or flag | What it does | | ------------------ | ------------------------------------ | | `id` | Conversation id, from `sondo inbox`. | | `--message ` | What the blocked visitor sees. | ```bash sondo conversation block 0f5c... --message "Contact us by email." ``` #### `sondo conversation unblock ` [#sondo-conversation-unblock-id] Unblock this visitor. | Argument or flag | What it does | | ---------------- | ------------------------------------ | | `id` | Conversation id, from `sondo inbox`. | ```bash sondo conversation unblock 0f5c... ``` #### `sondo conversation reply ` [#sondo-conversation-reply-id-text] Send an operator reply into a conversation. | Argument or flag | What it does | | ---------------- | ---------------- | | `id` | Conversation id. | | `text` | What to say. | ```bash sondo conversation reply 0f5c... "Thanks, booking a call now." ``` ### Knowledge Base and sites [#knowledge-base-and-sites] Load and maintain what the assistant answers from. #### `sondo kb list` [#sondo-kb-list] The documents in the Knowledge Base. | Argument or flag | What it does | | ---------------- | -------------------- | | `--page ` | Page number, from 1. | ```bash sondo kb list ``` #### `sondo kb get ` [#sondo-kb-get-id] One Knowledge Base document in full. | Argument or flag | What it does | | ---------------- | ---------------------------------- | | `id` | Document id, from `sondo kb list`. | ```bash sondo kb get 8b21... ``` #### `sondo kb add ` [#sondo-kb-add-sources] Add files, folders or web pages to the Knowledge Base. | Argument or flag | What it does | | ----------------- | ------------------------------------------------ | | `sources...` | Files, folders or http(s) addresses. | | `--title ` | Title for a single source. Ignored for a folder. | ```bash sondo kb add ./docs ./handbook.pdf https://example.com/pricing ``` #### `sondo kb update <id>` [#sondo-kb-update-id] Change a Knowledge Base document's title or text. | Argument or flag | What it does | | -------------------- | ---------------------------------------- | | `id` | Document id. | | `--title <title>` | New title. | | `--text-file <path>` | File whose contents become the new text. | ```bash sondo kb update 8b21... --text-file ./pricing.md ``` #### `sondo kb remove <id>` [#sondo-kb-remove-id] Delete a Knowledge Base document. | Argument or flag | What it does | | ---------------- | ------------ | | `id` | Document id. | ```bash sondo kb remove 8b21... ``` #### `sondo kb refresh <id>` [#sondo-kb-refresh-id] Fetch a linked page again and re-embed the document. | Argument or flag | What it does | | ---------------- | ------------ | | `id` | Document id. | ```bash sondo kb refresh 8b21... ``` #### `sondo site list` [#sondo-site-list] The connected sites and their sync state. ```bash sondo site list ``` #### `sondo site connect <url>` [#sondo-site-connect-url] Connect a site so its posts sync into the Knowledge Base. | Argument or flag | What it does | | ----------------- | ---------------------------------------------------- | | `url` | The site's root address. | | `--prefix <path>` | Only sync pages under this path. | | `--index <url>` | A sitemap or index page to read instead of guessing. | ```bash sondo site connect https://example.com --prefix /blog ``` #### `sondo site sync <id>` [#sondo-site-sync-id] Sync a connected site now. | Argument or flag | What it does | | ---------------- | -------------------------------- | | `id` | Site id, from `sondo site list`. | ```bash sondo site sync 4c90... ``` #### `sondo site remove <id>` [#sondo-site-remove-id] Disconnect a site. | Argument or flag | What it does | | ---------------- | ------------ | | `id` | Site id. | ```bash sondo site remove 4c90... ``` ### Configuration [#configuration] The workspace's settings, the widget, its embed snippet, and the keys and spend. #### `sondo settings get` [#sondo-settings-get] The workspace settings. ```bash sondo settings get ``` #### `sondo settings set <assignments...>` [#sondo-settings-set-assignments] Change workspace settings. One bad key fails the whole command. | Argument or flag | What it does | | ---------------- | --------------------------------------------------- | | `assignments...` | key=value pairs. Values that are not text are JSON. | ```bash sondo settings set timezone=Europe/Amsterdam resumeWindowHours=24 ``` #### `sondo widget get` [#sondo-widget-get] The widget's configuration. ```bash sondo widget get ``` #### `sondo widget snippet` [#sondo-widget-snippet] The embed snippet to paste into a site. ```bash sondo widget snippet ``` #### `sondo widget set <assignments...>` [#sondo-widget-set-assignments] Change the widget's configuration. One bad key fails the whole command. | Argument or flag | What it does | | ---------------- | --------------------------------------------------- | | `assignments...` | key=value pairs. Values that are not text are JSON. | ```bash sondo widget set headerTitle="Ask us" starters='["Pricing?","Book a call"]' ``` #### `sondo keys list` [#sondo-keys-list] The API keys for this workspace. ```bash sondo keys list ``` #### `sondo keys revoke <id>` [#sondo-keys-revoke-id] Revoke an API key. Issue new keys at Settings > Integrations. | Argument or flag | What it does | | ---------------- | ------------------------------- | | `id` | Key id, from `sondo keys list`. | ```bash sondo keys revoke 2ad7... ``` #### `sondo usage` [#sondo-usage] Model spend in US dollars. | Argument or flag | What it does | | ---------------- | ----------------------------- | | `--unit <unit>` | day (default), week or month. | ```bash sondo usage --unit week ``` # Getting started (/) Agents: the full docs are at [https://docs.usesondo.com/llms-full.txt](https://docs.usesondo.com/llms-full.txt), one page per section. Sondo is a chat widget you embed on your website. It answers visitors from a Knowledge Base you control, holds a real conversation, and hands you every transcript afterward. It answers from what you gave it, and says so when it does not know. Setting it up is four steps, and the dashboard tracks them for you. ## 1. Create your account [#1-create-your-account] Sign up at [usesondo.com/sign-up](https://usesondo.com/sign-up). There is no password to pick: you enter your email, and Sondo sends a link that signs you in. You can also continue with Google. ## 2. Choose a plan [#2-choose-a-plan] Every feature is on every plan. They differ only in how much they hold: how many workspaces you can own, and how large each workspace's Knowledge Base can be. See [API keys and plans](/api-keys-and-plans) for the numbers, or pick one at [usesondo.com/pricing](https://usesondo.com/pricing). There is no trial. Your widget turns on when the subscription is active. ## 3. Create your workspace [#3-create-your-workspace] A workspace holds one site's Knowledge Base, its assistant persona, and its embeddable chat. You give it a name and the address of the site you will embed it on. Sondo uses that address to decide which sites are allowed to load your widget, so it is worth getting right, though you can change it later in Settings. ## 4. Work through the checklist [#4-work-through-the-checklist] The dashboard shows a card called **Get your assistant live** with four steps. Each one ticks itself once it is true, so there is nothing to mark done. 1. **Add your OpenRouter key.** Sondo uses your own [OpenRouter](https://openrouter.ai) account for the model calls, so you pay for what your assistant actually costs and can see every dollar of it in your dashboard. Add the key under Settings, in the Models room. 2. **Add Knowledge Base content.** Paste text, upload a file, fetch a URL, or connect your blog. This is what your assistant answers from, and it will not invent anything beyond it. See [Knowledge Base](/knowledge-base). 3. **Set your assistant's persona.** Who it is, how it speaks, what it should do when it cannot answer. Settings, in the Assistant room. 4. **See it live on your site.** Copy the embed snippet and paste it into your site. See [Install the widget](/install-the-widget). This step ticks itself when your first visitor chats. You can close the checklist once you are done with it. If you want it back, Settings has a General room with a row called Getting started and a button that puts it back on the dashboard. ## What to read next [#what-to-read-next] * [Install the widget](/install-the-widget) for the snippet and its options. * [Knowledge Base](/knowledge-base) for every way to give your assistant something to answer from. * [API keys and plans](/api-keys-and-plans) to connect an AI client to your workspace. # Install the widget (/install-the-widget) Your widget is one line of HTML. You get it from Settings, in the Widget room, under **Embed snippet**. The Settings landing page shows it too. There are two shapes, and both can be live on the same site at once. The picker in the dashboard only chooses which one you are copying. ## The floating orb [#the-floating-orb] A launcher orb in the corner of the page that opens the chat when a visitor clicks it. This is the usual choice. ```html <script src="https://usesondo.com/w/v1/loader.js" data-sensor-key="wgt_pk_your_key" data-theme="auto" async ></script> ``` Paste it just before the closing `</body>` tag on any page you want the chat on. Saved changes to your assistant apply within a minute, and you never have to re-embed to pick them up. ### The attributes [#the-attributes] | Attribute | What it does | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `data-sensor-key` | Your widget's public key, which always starts with `wgt_pk_`. Required. It is not a secret: it ships in the HTML of every page you put it on. | | `data-theme` | `auto`, `light` or `dark`. `auto` follows the visitor's own system setting. Leave it out and the widget is light. | | `data-sensor-section` | Optional, and it goes on your own elements rather than the script tag. See below. | The attribute names start with `sensor` because that was the product's name when the embed contract was written. They are frozen: renaming them would break every site that has already pasted the tag. ### Telling the assistant what the visitor is reading [#telling-the-assistant-what-the-visitor-is-reading] Put `data-sensor-section="Pricing"` on a section of your page, and when a visitor opens the chat while that section is on screen, the assistant is told which part of the page they are looking at. Pages with no annotated sections simply send nothing. Around two thousand characters of the section's text are forwarded. ## The docked panel [#the-docked-panel] A panel that fills a container you place on the page, rather than floating over it. Useful for a support page or a help center, where the chat is the content rather than an offer. ```html <iframe src="https://usesondo.com/w/v1/frame?key=wgt_pk_your_key&docked=1&theme=light" style="width:100%;height:100%;border:0" title="Chat assistant" ></iframe> ``` The frame fills its container, so give the parent an explicit height, for example `600px`, or it collapses to nothing. Add `theme=dark` to the URL for the dark palette. If your page has its own light and dark toggle, you can tell the frame about it at any time by posting a message to it: ```js frame.contentWindow.postMessage({ type: "sensor:theme", theme: "dark" }, "https://usesondo.com"); ``` ## Things worth knowing [#things-worth-knowing] * **One script per page.** A second copy of the loader on the same page does nothing. If a page has more than one tag, the first one wins. * **A tag with no key does nothing at all.** The loader looks for a script carrying `data-sensor-key`, and if there is not one, or the key is empty, it stops silently rather than injecting anything. * **The launcher does not appear until your workspace can answer.** A workspace with no model key configured serves no widget, which is why the Getting started checklist puts that step first. * **Your site decides who may embed it.** Sondo seeds the allowed sites from the website address you gave when you created the workspace. If your widget does not load on a new domain, that list in Settings is the first thing to check. # Knowledge Base (/knowledge-base) The Knowledge Base is the set of sources your assistant grounds its answers in. It answers from what is here and nothing else, so what you put in is the whole of what it knows. Open it from the dashboard's Knowledge Base page. The header shows how much of your plan's budget you have used, in pages. ## Adding a source [#adding-a-source] The **Add source** button opens a form with four tabs. ### Paste [#paste] For anything you can copy: a policy, a price list, an internal note nobody has published. Give it a title, paste the text, and it is searchable within moments. ### Upload [#upload] Text-based PDF, Markdown, or plain text, up to 2 MB per file. The title defaults to the filename. Scanned PDFs do not work. A scan is a picture of a page, and Sondo reads text rather than running optical character recognition, so a PDF with no extractable text is refused rather than silently added as an empty source. Export a text PDF from the original document instead. ### URL [#url] Fetches one page and extracts its main content. Login-walled pages do not work, since Sondo visits the page as an anonymous reader. A URL source keeps its address, so you can refetch it later when the page changes. ### Site [#site] Connect a blog and every post becomes a source, kept up to date every night on its own. Give it the root address of the site. Sondo reads your `llms.txt` file and your sitemap together and combines what they list, since each one knows something the other does not: the `llms.txt` carries the titles and descriptions you wrote, and the sitemap carries the dates that tell Sondo what has changed. An RSS or Atom feed is tried only if neither of those turns up a post. If none of them does, Sondo asks you for the address of a sitemap or feed directly, and a pasted address is used on its own. You can restrict a connected site to one path prefix, for example `/blog/`, so only the pages under it are synced. Leave it empty to take every post found. Each sync reads up to fifty posts per site, spaced out so your server is not hammered, and skips anything under about a hundred and twenty characters as too short to be useful. Sondo identifies itself as `SondoBot/1.0` in every request. The **Sync now** button on a site's card runs a sync immediately, in the background. Posts that come from a connected site are read only in Sondo: to change what your assistant says about one, edit the post on your own site, and the next sync picks it up. ## Keeping a source current [#keeping-a-source-current] Open any source from the Knowledge Base list. * **Edit a document in place.** Change the title or the text and save. Sondo re-slices and re-embeds it, and swaps the new version in atomically, so the assistant is never mid-way between two versions of the same source. A shrinking edit always fits, because only the growth counts against your budget. * **Refetch a URL source**, or point it at a different address, from the same page. A refetch replaces any hand edits you made to that source's text, which is the trade for keeping it in sync with the page. * **Re-index** rebuilds the searchable slices without changing the text. It is the thing to try when a source looks present but never turns up in an answer. * **Delete** removes it from the Knowledge Base entirely. The detail page also has a Chunks tab, which shows the slices the assistant actually searches, and whether each one is searchable yet. ## Limits [#limits] * **One source holds up to 200,000 characters** of extracted text, which is about a hundred printed pages. This applies whether you pasted it, uploaded it, or fetched it. * **Your workspace's total is your plan's page budget.** See [API keys and plans](/api-keys-and-plans) for each plan's number, and for what a page is. * **Re-indexing and deleting are never blocked by the budget.** Only adding and growing count, so a full Knowledge Base can always be repaired or trimmed. * **A connected site stops at the budget** rather than adding some posts and not others. Its card says so, and the sync resumes once you have room. Adding anything also needs a working model key on the workspace, because a new source has to be embedded before it can be searched, and that is a paid call on your own account. # MCP server and clients (/mcp) Sondo speaks the Model Context Protocol, so an AI client can work with your workspace from wherever you already are: read the conversations your widget had overnight, reply to one as yourself, add a page to the Knowledge Base, or change what the assistant is allowed to say. The one endpoint every client connects to is `https://usesondo.com/api/mcp`. The tools cover the work the dashboard does day to day: conversations, the Knowledge Base, connected sites, the workspace and widget settings, the API keys and the model spend. Billing, creating a workspace, issuing a key and the outbound webhook stay in the dashboard. Nothing here takes a workspace argument. The key you connect with decides which workspace you are in, and it cannot reach another one. ## Get a key first [#get-a-key-first] Every client authenticates with its own API key from the Integrations page in your dashboard. It starts with `sondo_sk_`, it is shown once, and you can revoke it without touching your other clients. See [API keys and plans](/api-keys-and-plans) for how to issue one. There is no OAuth. The key travels as a bearer token on the `Authorization` header, which is why every command below carries one. ## Connect your client [#connect-your-client] Replace the placeholder key in each command with your own. ### Claude Code [#claude-code] Run this once: ```bash claude mcp add --scope user --transport http sondo https://usesondo.com/api/mcp --header "Authorization: Bearer sondo_sk_…" ``` Issue the key under Settings > Integrations first. It is shown once. ### Claude Desktop [#claude-desktop] Settings → Developer → Edit Config (claude\_desktop\_config.json), then restart the app. ```json { "mcpServers": { "sondo": { "type": "http", "url": "https://usesondo.com/api/mcp", "headers": { "Authorization": "Bearer sondo_sk_…" } } } } ``` ### Cursor [#cursor] Add this to your Cursor MCP settings file: ```bash { "mcpServers": { "sondo": { "type": "http", "url": "https://usesondo.com/api/mcp", "headers": { "Authorization": "Bearer sondo_sk_…" } } } } ``` Paste your key from Settings > Integrations, then enable Sondo in Cursor. ### VS Code [#vs-code] Add this to .vscode/mcp.json: ```bash { "mcpServers": { "sondo": { "type": "http", "url": "https://usesondo.com/api/mcp", "headers": { "Authorization": "Bearer sondo_sk_…" } } } } ``` Paste your key from Settings > Integrations, then start the server. ### Codex CLI [#codex-cli] Run this once: ```bash codex mcp add sondo --url https://usesondo.com/api/mcp --bearer-token-env-var SONDO_API_KEY ``` Export SONDO\_API\_KEY with your key from Settings > Integrations, then start codex. Codex reads that environment variable when it starts, not when you export it, so export it and then start Codex, in that order. ### Gemini CLI [#gemini-cli] Run this once: ```bash gemini mcp add --transport http --header "Authorization: Bearer sondo_sk_…" sondo https://usesondo.com/api/mcp ``` Issue the key under Settings > Integrations first. It is shown once. ### Anything else [#anything-else] Point it at the endpoint and send the key on this header: ```bash https://usesondo.com/api/mcp Authorization: Bearer sondo_sk_… ``` Issue the key under Settings > Integrations. It is shown once. Chat products that connect through their own interface, including Claude, ChatGPT, Grok and Meta Muse, need the same two things: the endpoint, and the key on that header. Where their interface puts those fields is their business, and it changes often enough that this page will not guess. ## Check that it worked [#check-that-it-worked] Ask your client to use Sondo's `ping` tool by name. It answers with the workspace it is connected to, which is the fastest way to tell a working key from a working connection to the wrong workspace. Naming the tool matters. Asked loosely, a coding agent will often reach for `curl` instead, which proves only that the endpoint is reachable. ## Tool reference [#tool-reference] Generated from the server, so it cannot drift from what your client will actually find. An unknown id, or one from another workspace, comes back as "not found" rather than an error about permission, because a permission error would confirm that the thing exists. Sondo exposes 27 tools. ### Conversations [#conversations] Read what visitors asked, reply as the operator, and keep the list tidy. #### `conversation_reply` [#conversation_reply] Write your own reply into a conversation, as the operator. The visitor sees it within a few seconds. This is not a takeover: the assistant still answers their next message, having read your reply. Pause it first with conversation\_set if you want the thread. | Argument | Type | Notes | | --------- | ------ | --------------------- | | `id` | uuid | | | `content` | string | 1 to 4,000 characters | #### `conversation_set` [#conversation_set] Set a conversation's star, block its visitor, or pause the assistant in it. Pass at least one of starred, visitorBlocked or aiPaused. Read and archived belong to set\_read and set\_archived. | Argument | Type | Notes | | --------------------------- | ------- | --------------------------------------------------------------------------------------------------------- | | `id` | uuid | | | `starred` (optional) | boolean | | | `visitorBlocked` (optional) | boolean | | | `blockMessage` (optional) | string | What a blocked visitor is told. Only meaningful when blocking; unblocking clears it. up to 300 characters | | `aiPaused` (optional) | boolean | Paused, the assistant stops answering until you resume it. Manual only | #### `get_conversation` [#get_conversation] The full transcript of one conversation, plus visitor context, signals and model cost. Ids come from list\_conversations. | Argument | Type | Notes | | -------- | ---- | ----- | | `id` | uuid | | #### `list_conversations` [#list_conversations] Overview of this workspace's conversations: the total, then the 10 most recent by activity (id, title, unread). Start here and stay zoomed out. Pass detail:'summaries' for a gist and triage signals per conversation, or get\_conversation for one full transcript. | Argument | Type | Notes | | ------------------- | ---------------------------------- | ------------------------------------------------------ | | `view` (optional) | one of `all`, `unread`, `archived` | all (default, excludes archived) \| unread \| archived | | `q` (optional) | string | Search titles, messages and visitor emails | | `detail` (optional) | one of `titles`, `summaries` | Default titles | | `page` (optional) | integer | at least 1 | #### `set_archived` [#set_archived] Archive one conversation (out of the inbox) or restore it. Archiving is reversible and deletes nothing. | Argument | Type | Notes | | --------------------- | ------- | ------------ | | `id` | uuid | | | `archived` (optional) | boolean | Default true | #### `set_read` [#set_read] Mark one conversation read/unread by id, or pass id:'all' to clear the whole inbox. 'all' only marks READ; there is no bulk unread. | Argument | Type | Notes | | ----------------- | ------------- | ------------ | | `id` | uuid or `all` | | | `read` (optional) | boolean | Default true | #### `unread_count` [#unread_count] How many conversations are unread (excludes archived). Takes no arguments. Takes no arguments. ### Knowledge Base [#knowledge-base] Everything the Add source form does, plus editing and removing what is already there. #### `kb_add` [#kb_add] Add a document by pasting its text. Chunked and embedded at once, so the assistant can use it on the next message. Costs pages from the plan's allowance. | Argument | Type | Notes | | -------- | ------ | ----------------------- | | `title` | string | 1 to 200 characters | | `text` | string | 1 to 200,000 characters | #### `kb_add_file` [#kb_add_file] Add a text-based PDF, Markdown or plain-text file, base64-encoded. Up to 2 MB decoded. A scanned, image-only PDF has no text to extract and is refused. | Argument | Type | Notes | | ------------------ | ------ | ------------------------------------------------------------- | | `filename` | string | Used to pick the extractor and the title. 1 to 255 characters | | `contentBase64` | string | at least 1 characters | | `title` (optional) | string | 1 to 200 characters | #### `kb_add_url` [#kb_add_url] Fetch a public page and add its main content as a document. The address is checked against private networks before anything is fetched. Title defaults to the page's own. | Argument | Type | Notes | | ------------------ | ------ | ---------------------- | | `url` | URL | up to 2,048 characters | | `title` (optional) | string | 1 to 200 characters | #### `kb_get` [#kb_get] One document's stored text, with its title, source and size in pages. Ids come from kb\_list. This is the text the assistant retrieves from. | Argument | Type | Notes | | -------- | ---- | ----- | | `id` | uuid | | #### `kb_list` [#kb_list] This workspace's Knowledge Base: how many pages of the plan's allowance are used, then one line per document (id, title, where it came from, status, chunks). No text. Use kb\_get for one document's content. | Argument | Type | Notes | | ----------------- | ------- | ---------- | | `page` (optional) | integer | at least 1 | #### `kb_refresh` [#kb_refresh] Bring one document up to date. A document added from a URL is fetched again from that address, overwriting any hand edits. Every other document is re-chunked and re-embedded from its stored text. Not for a site's posts, which their site syncs. | Argument | Type | Notes | | -------- | ---- | ----- | | `id` | uuid | | #### `kb_remove` [#kb_remove] Delete a document and its chunks. The assistant stops retrieving from it immediately, and past answers that cited it read as 'source removed'. A site's posts are removed by removing the site. | Argument | Type | Notes | | -------- | ---- | ----- | | `id` | uuid | | #### `kb_update` [#kb_update] Replace a document's title, its text, or both. Re-chunks and re-embeds. Omitted fields keep what is stored. A document synced from a connected site cannot be edited here. | Argument | Type | Notes | | ------------------ | ------ | ----------------------- | | `id` | uuid | | | `title` (optional) | string | 1 to 200 characters | | `text` (optional) | string | 1 to 200,000 characters | ### Connected sites [#connected-sites] Connect a blog, sync it, and disconnect it. #### `site_connect` [#site_connect] Connect a blog so its posts sync into the Knowledge Base. Posts are found from the site's sitemap, feed or llms.txt; pass manualIndexUrl (same host) if it has none. The first sync starts in the background. Read site\_list for its progress. | Argument | Type | Notes | | --------------------------- | ----------------------- | ---------------------- | | `rootUrl` | URL | up to 2,048 characters | | `pathPrefix` (optional) | string matching `^\/.*` | up to 200 characters | | `manualIndexUrl` (optional) | URL | up to 2,048 characters | #### `site_list` [#site_list] Every site connected to this workspace: where it is, how its posts were found, how many it holds, and what its last sync did. Read this after site\_connect or site\_sync to see how the run ended. Takes no arguments. #### `site_remove` [#site_remove] Disconnect a site and delete every post it synced. The assistant stops retrieving from them at once. Nothing else in the Knowledge Base is touched. | Argument | Type | Notes | | -------- | ---- | ----- | | `id` | uuid | | #### `site_sync` [#site_sync] Re-run one site's sync: fetch its index, then re-embed the posts that changed. Runs in the background and returns at once. Read site\_list for progress and the result. | Argument | Type | Notes | | -------- | ---- | ----- | | `id` | uuid | | ### Settings and the widget [#settings-and-the-widget] The workspace's own configuration, and how the chat looks and behaves. #### `settings_get` [#settings_get] Everything settings\_update can change, as it is now, plus the plan and how much of its Knowledge Base allowance is used. The OpenRouter key is reported as configured or not, never as its value. Takes no arguments. #### `settings_update` [#settings_update] Change any of the workspace's settings. Pass only the keys you want changed; the rest are left alone. Validation is all or nothing: if any key is rejected, nothing at all is written. guardrails and responseStyle are whole objects, and postSuggestions and postCards must be sent together. | Argument | Type | Notes | | ------------------------------ | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` (optional) | string | The workspace's display name. 1 to 64 characters | | `websiteUrl` (optional) | string | The workspace's own website as a full http(s) URL, or "" to clear it. Its icon is fetched too. up to 2,048 characters | | `timezone` (optional) | string, or `""` to clear it | An IANA name such as "Europe/Amsterdam", or "" for Auto. 1 to 64 characters | | `persona` (optional) | string | How the assistant should behave. up to 8,000 characters | | `guardrails` (optional) | object with `allowCode` (required), `allowCreative` (required), `allowGeneralQA` (required) | All three flags, always. Which deliverables the assistant may produce | | `guardrails.allowCode` | boolean | | | `guardrails.allowCreative` | boolean | | | `guardrails.allowGeneralQA` | boolean | | | `responseStyle` (optional) | object with `enabled` (required), `level` (required) | Both fields, always. level 1 is briefest, 5 is fullest | | `responseStyle.enabled` | boolean | | | `responseStyle.level` | one of `1`, `2`, `3`, `4`, `5` | | | `resumeWindowHours` (optional) | number | How long a returning visitor picks up where they left off. One of: 1, 6, 24, 48, 168, 336 (hours) | | `postSuggestions` (optional) | one of `never`, `when_relevant`, `only_when_asked` | Send with postCards | | `postCards` (optional) | boolean | Send with postSuggestions | | `models` (optional) | object with `chat`, `summary`, `extract` | A provider/model slug pins a task; null clears it back to the default | | `models.chat` (optional) | string matching `^[\w.-]+\/[\w.:-]+$`, or `null` | 1 to 120 characters | | `models.summary` (optional) | string matching `^[\w.-]+\/[\w.:-]+$`, or `null` | 1 to 120 characters | | `models.extract` (optional) | string matching `^[\w.-]+\/[\w.:-]+$`, or `null` | 1 to 120 characters | | `openrouterKey` (optional) | string, or `""` to clear it | Write-only; it is never read back. "" REMOVES the key, which pauses the assistant for every visitor until a new one is saved. There is no server key to fall back to. 20 to 300 characters | #### `widget_get` [#widget_get] What visitors see and where the widget may load: its name, header, welcome message, suggested questions, booking link, showcase cards, colors, allowed origins, and the embed snippet to paste into a site. Takes no arguments. #### `widget_update` [#widget_update] Change any of the widget's settings. Pass only what you want changed; everything else keeps its current value. An array (starters, showcase, allowedOrigins) is replaced whole, so pass the full list, and \[] really does clear it. | Argument | Type | Notes | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | | `name` (optional) | string | 1 to 80 characters | | `welcomeMessage` (optional) | string | up to 500 characters | | `bookingUrl` (optional) | URL, or `""` to clear it | up to 2,048 characters | | `starters` (optional) | array (up to 8) of string | | | `headerTitle` (optional) | string | up to 80 characters | | `showHeader` (optional) | boolean | | | `chatOrb` (optional) | boolean | | | `colors` (optional) | object with `light` (required), `dark` (required) | Both modes, each #rrggbb or "" | | `colors.light` | object with `primary` (required), `userBubble` (required), `userBubbleForeground` (required), `assistantBubble` (required), `assistantBubbleForeground` (required) | | | `colors.dark` | object with `primary` (required), `userBubble` (required), `userBubbleForeground` (required), `assistantBubble` (required), `assistantBubbleForeground` (required) | | | `shader` (optional) | string | The launcher orb's shader key. 1 to 64 characters | | `emailCapture` (optional) | object with `contactReason` (required), `gapReason` (required) | | | `emailCapture.contactReason` | string | up to 300 characters | | `emailCapture.gapReason` | string | up to 300 characters | | `contactEmail` (optional) | email, or `""` to clear it | up to 200 characters | | `showcase` (optional) | array (up to 6) of object with `title` (required), `description` (required), `imageUrl` (required), `url` (required) | | | `showcase[].title` | string | 1 to 80 characters | | `showcase[].description` | string | up to 140 characters | | `showcase[].imageUrl` | URL or string matching `^\/(?!\/)\S*$`, or `""` to clear it | up to 2,048 characters | | `showcase[].url` | URL, or `""` to clear it | up to 2,048 characters | | `allowedOrigins` (optional) | array (up to 20) of string | Sites the widget may be embedded on. Replaces the whole list; \[] blocks every external embed | | `resumeWindowHours` (optional) | number | | ### API keys [#api-keys] List and revoke the keys other clients connect with. #### `key_list` [#key_list] Every API key for this workspace, oldest first: label, when it was made, when it was last used, and whether it is revoked. Key values are never stored and never shown. Issue a new key from the dashboard's Integrations page. Takes no arguments. #### `key_revoke` [#key_revoke] Revoke one API key by id, from key\_list. The row survives as a record, with its revoke date. Revoking the key you are calling with is allowed, and ends this session's access immediately. | Argument | Type | Notes | | -------- | ---- | ----- | | `id` | uuid | | ### Everything else [#everything-else] A connectivity check, and what this workspace has spent. #### `ping` [#ping] Check the Sondo connection and report which workspace this API key opens. Takes no arguments. Takes no arguments. #### `usage_summary` [#usage_summary] Model spend in USD, bucketed by day (last 30), week (12) or month (12). UTC calendar days. | Argument | Type | Notes | | ----------------- | ----------------------------- | ----- | | `unit` (optional) | one of `day`, `week`, `month` | |