KnowScapes AI Client
AI chat widget for your site. Use your own OpenAI, Anthropic or OpenRouter key, or a free KnowScapes Community key.
- Version 2.3.0
- Requires WordPress 6.5
- Tested up to 7.1
- Requires PHP 7.4
- License GPLv2 or later
- As of 2 October 2026
Overview
KnowScapes AI Client adds a floating chat assistant to your site. Visitors type a question, the reply is generated by the AI provider you selected, and the answer is rendered as formatted text inside the chat panel.
You choose where the answers come from:
- KnowScapes Community (free key) — a retrieval-augmented agent run by xplicator GmbH that answers from your own website. You can request a free key directly from the settings screen, so no account with a commercial AI provider is needed to get started.
- KnowScapes (paid plan) — the same service and the same endpoint, with a key from a paid plan instead of the fair-use allowance. Both entries keep their own key, so you can hold a free key and a paid one side by side and switch between them without pasting a key again.
- OpenAI — with your own API key, billed to your own OpenAI account.
- Anthropic — with your own API key, billed to your own Anthropic account.
- OpenRouter — with your own API key, billed to your own OpenRouter account.
- Any OpenAI-compatible endpoint — for example a self-hosted gateway on your own infrastructure.
Where the chat appears is up to you: a floating launcher on every page, only on
the content types you pick, or nowhere at all — dropped into a post, a page or a
template with the [knowscapes_ai_chat] shortcode instead. Individual entries
can be excluded, the launcher can sit in either bottom corner and can be hidden
on phones, and the whole assistant can be kept for signed-in users only.
Two things make a first message more likely and cost nothing until they are used: starter questions, shown as buttons in the empty chat, and page context, which lets the assistant answer about the page the visitor is actually reading. Every reply carries a copy button.
Every chat shows a short notice that the visitor is talking to an AI and that replies may contain mistakes. You can reword it — into the language of your site, for instance — but not switch it off, because the EU AI Act requires that people can tell when they are talking to an AI.
Every feature of the plugin is available with every provider. Nothing in the plugin is switched off, time-limited or gated behind a payment: which provider you use, and what that provider charges you, is entirely your decision.
The KnowScapes Community plan
The Community plan is KnowScapes' free plan. On the KnowScapes side it covers one website as the knowledge source plus one additional document, and the free key answers a limited number of messages per hour and per day for the site it is used on — at the time of writing 20 per hour and 480 per day, roughly three to six visitor conversations an hour, which suits a small site. A short burst above the hourly rate is absorbed rather than refused.
When the allowance is used up, the service declines further messages until the next period. Nothing about the plugin changes: no feature is switched off, no countdown starts, and every setting keeps working. Visitors see a short notice that the assistant is unavailable for now, and you see the reason and the current allowance on the plugin's settings screen. The current figures — messages, pages and document size — are published at https://knowscapes.org/free-key-limits/ and the plans at https://knowscapes.org/pricing/
You are never obliged to use this key. The same plugin works with your own OpenAI, Anthropic or OpenRouter key, or with your own endpoint, and it behaves identically either way.
Your API key stays on the server
Visitors never see your key. The browser talks only to a REST route on your own WordPress site; that route adds the key and forwards the request. The key is stored in the WordPress options table and is never printed into a page, a script variable or a response body. The settings screen only ever shows it masked.
Nothing happens until you say so
After activation the plugin is inert. It has no key, no provider endpoint it can reach, and no widget on your site, and it never contacts anything on its own — not on activation, not when an admin page loads, not when a visitor views your site.
Three things can send a request, and each one is something you start:
- Pressing Request free key sends your e-mail address and this site's URL to knowscapes.com, and only after you tick the box next to that button.
- Entering an API key, or pressing Load available entries, asks the selected provider which models your key may use. Installing a free key through the link in its e-mail does this once as well, as part of that same button press. Opening the settings screen alone never does.
- A visitor sending a chat message, which is possible only once you have supplied a key, chosen a model, confirmed the data transfer and switched the widget on.
No visitor data of any kind leaves your site before that last step.
Features
- Floating chat launcher with ten icons to pick from
- Light, dark or automatic colour scheme, and a configurable accent colour
- Markdown replies, sanitised before they are inserted into the page
- Optional collapsible reasoning block for reasoning-capable models
- Configurable system prompt, reply length cap and per-visitor rate limit
- Greeting message that costs nothing, because it is never sent to the provider
- AI notice under every chat, rewordable but always shown
- Uninstall routine that removes its option and its transients, across every site of a multisite network
Installation
- Upload the plugin through Plugins → Add New → Upload Plugin, then activate it.
- Open KnowScapes AI in the admin menu.
- Pick a provider.
- Enter that provider's API key. For KnowScapes you can instead request a free key right under the API key field: enter your e-mail address, confirm the transfer, press the button, then either press the button in the e-mail you receive or paste the key into the API key field.
- The list of models or agents loads by itself once the key is entered, and the first entry is selected. Pick another from the dropdown if you prefer, or choose Enter manually… for an endpoint that publishes no list.
- Tick the consent box, tick Show the chat widget on the front end, and save.
- The launcher now appears in the bottom-right corner of your site. To put the chat inside a post, a page or a template instead, use the
[knowscapes_ai_chat]shortcode and set Where the launcher appears to the shortcode-only option.
Administrator's guide
Every setting on the KnowScapes AI screen, what it does, and what it costs. For plugin version 2.3.0.
This chapter walks the settings screen top to bottom. Where it and the plugin's readme.txt differ, readme.txt is the shipped text and wins.
1. Getting to a working chat
Five things have to be true before the widget appears, and the panel at the top of the settings screen lists whichever are missing:
- A provider is selected and a key is stored for it.
- A model or agent is chosen.
- The data-transfer box is ticked.
- The widget is switched on.
- The endpoint resolves — automatic for every provider except the custom one.
Until then nothing is enqueued on the front end and no request leaves the site. That is deliberate: a fresh install is inert.
The shortest path with a free KnowScapes key:
- Leave the provider on KnowScapes Community (free key).
- Directly under the API key field, in Request a free KnowScapes key, check the address, tick the confirmation, press the button.
- Open the e-mail. Either press its button — which brings you back to a confirmation screen on your own site — or copy the key into the API key field and save.
- The agent list loads by itself once the key is in the field, and the first entry is selected. A free key is bound to one agent, so that is the right one; the install link does all of this for you.
- Tick the data-transfer box, tick Show the chat widget on the front end, save.
2. AI provider
Provider
| Entry | Endpoint | Billed by |
|---|---|---|
| KnowScapes Community (free key) | knowscapes.com/api |
nobody — Community plan allowance |
| KnowScapes (paid plan) | knowscapes.com/api |
KnowScapes |
| OpenAI | api.openai.com |
OpenAI |
| Anthropic | api.anthropic.com |
Anthropic |
| OpenRouter | openrouter.ai/api |
OpenRouter |
| Custom OpenAI-compatible endpoint | whatever you enter | nobody, or you |
The two KnowScapes entries are the same service at the same address. They are separate entries because a free key and a paid key are different credentials with different allowances, and because each entry keeps its own key: you can hold both and switch between them without pasting a key again. The free-key request form only appears while the free entry is selected.
Anthropic speaks a different request dialect (/v1/messages) from the others
(/v1/chat/completions). The plugin handles that; you do not have to care,
except that a model name from one vendor will not work with another.
Endpoint base URL
Only shown for the custom provider. The part before /v1, https only — a plain
http endpoint is refused, because the API key would travel unencrypted. If you
mistype it, the settings screen tells you and the field is cleared rather than
saved wrong.
API key
Stored per provider, on the server, in the knowscapes_ai_client_settings
option. It never reaches the browser — not even a masked prefix. The field shows
a mask of the stored key as its placeholder and is always submitted empty, so
leaving it alone keeps what is stored.
No key yet? While the free KnowScapes entry is selected, the row under the key field offers a free key: e-mail address, a confirmation box naming the transfer, and a button that stays disabled until the box is ticked. The request is sent separately from the settings — pressing it saves nothing else, and Save never sends it. The row is open while no key is stored and folds into one line once one is.
To remove a key, tick the delete box that appears under the field. The box names the provider it belongs to.
One rule worth knowing: if you change the provider and type a key in the same save, the key is ignored and you get a warning. The field belonged to the provider that was selected when the page was rendered, and storing one vendor's credential under another would later send it to the wrong company. Save the switch first, then enter the key.
Model / Agent
A dropdown filled with the provider's own list. It loads by itself as soon as you enter a key — after a paste, when you leave the field, or after a short pause in typing — and selects the first entry, unless the current one is in the list. Load available entries loads it again, using the key in the field or the stored one. Each load is one outbound request to the provider; opening the settings screen alone sends none. Save afterwards; loading the list does not save anything.
The last entry, Enter manually…, turns the dropdown back into a text field for an endpoint that publishes no list, or for a model the list does not show. For OpenAI and OpenRouter the list is long and sorted by name, so the first entry is not necessarily a chat model — check the selection before you save.
3. Consent and activation
Data transfer
The widget stays hidden and no visitor message leaves the site until this is ticked. It names the provider you selected and links to that provider's terms and privacy policy.
The plugin makes exactly three kinds of outbound request, and each one follows an action of yours: a visitor's message, your press of Load available entries, and your press of Request free key. Data flow and privacy lists all three in full.
Chat widget
The master switch for the front end. It cannot be turned on without consent.
4. Behaviour
System prompt
Instructions prepended to every conversation. Leave it empty when the provider-side agent already carries its own instructions — which is the normal case for a KnowScapes agent, and why it starts empty.
Four placeholders are available, filled in on the server from the page the visitor is reading:
| Placeholder | Becomes |
|---|---|
{title} |
the page title, tags stripped |
{url} |
the page's permalink |
{site_name} |
your site's name |
{content} |
the page's text, as plain text, up to the limit below |
They only work while Page context is on. With it off, they are removed from
the prompt rather than left in it, because a model shown the literal text
{content} will start talking about the placeholder.
A prompt with no placeholders in it is passed through untouched, so this costs nothing if you do not use it.
Page context
Off by default, because it changes what leaves your site.
With it on, the title, address and text of the page the visitor is reading travel to the provider with every message. That is what lets the assistant answer "what does this page say about returns?" without a knowledge base.
What it will never read. The page is identified by an ID that arrives from the visitor's browser, so it has to be treated as something anyone can change. The plugin refuses anything that is not fully public: drafts, scheduled, pending and private posts, password-protected posts, revisions, autosaves, post types that are not publicly queryable, and unknown or nonsensical IDs. Text is taken from the stored content with shortcodes stripped rather than executed, and editor block comments removed. There is a test for each of those refusals.
Switching it on also adds a paragraph to the privacy-policy text the plugin suggests under Tools → Privacy. Copy it into your policy.
Page context limit
How much of the page to pass on, from the top. 200 to 20000 characters, 4000 by default. Everything you send here counts towards what the provider charges for the request, on every message of every conversation — this is the setting that decides whether page context is cheap or expensive.
Maximum reply length
The room a single reply gets, in tokens. 64 to 32768, 4096 by default, which is roughly 3,000 words.
Set this too low and replies stop mid-sentence, which to a visitor looks like the assistant breaking. Only the tokens actually used are billed, so a generous limit costs nothing by itself — the reason for a ceiling at all is to bound what a single request can cost.
The plugin now notices when a reply hits the limit: the visitor gets a neutral line saying it was cut short and that they can ask for the rest, and this screen shows which limit was hit and suggests a higher one. That notice clears itself once you raise the limit.
If you use a KnowScapes key and replies are still cut off after raising this, the cap on the service side is lower than what the plugin asks for — the two are combined by taking the smaller. Ask support to raise the profile on your key.
Rate limit
Messages per minute per visitor, counted by the address your web server reports. Forwarded-for headers are deliberately not trusted, because anyone can send one. Behind a CDN or reverse proxy that address is the proxy, so every visitor shares one budget; if you trust your proxy, supply the real address:
add_filter( 'knowscapes_ai_client_client_ip', function () {
return $_SERVER['HTTP_CF_CONNECTING_IP'] ?? $_SERVER['REMOTE_ADDR'];
} );
Rate limit, signed in
Messages per minute per signed-in account, 30 by default. Signed-in visitors are counted by user ID instead of address, which is both a better identifier and fairer: colleagues behind one office router no longer share a single budget.
Daily ceiling
Messages per day across the whole site, 500 by default, 0 for none. This is the one that bounds the bill under abuse, because the per-visitor limit can be sidestepped by changing address.
It is best-effort rather than exact. WordPress offers no atomic counter that works both with and without a persistent object cache, so a burst of genuinely simultaneous requests can slip a few past. It undercounts rather than over, so it will not lock visitors out early. Treat it as a guardrail and set a hard spending limit at your provider as well if the bill matters to you.
Only messages that actually reach the provider are counted, so malformed requests cannot exhaust the budget.
Reasoning
Shows the contents of <think> blocks, which some reasoning models emit, in a
collapsible block. Off by default.
5. Placement and access
Where the launcher appears
| Choice | Effect |
|---|---|
| On every page | the floating launcher everywhere |
| Only on the content types selected below | single entries of those types |
| Nowhere — place it by shortcode only | no launcher at all |
"Only on the content types selected" means single entries. An archive, the blog index and a search results page are not single entries and never match — if you want the chat there, use "on every page" and exclude what you do not want.
Content types
Shown only for the middle choice. Public post types, media excluded.
Never on these entries
Post or page IDs, comma-separated. The launcher is suppressed on these whatever the rule above says — a checkout, a contact page, a landing page you want kept clean. A shortcode placed inside the entry itself still works; putting it there is the decision.
To find an ID, open the entry in the editor and read post=1234 out of the URL.
Who may use it
Signed-in users only both hides the chat and refuses the REST request behind it. That second part matters: the route is public, so hiding a launcher protects nothing on its own. Pick this for an internal assistant, or to keep a fair-use allowance for your own team.
6. The shortcode
[knowscapes_ai_chat]
[knowscapes_ai_chat height="600"]
Puts the chat into the flow of a post, a page, a block or a template instead of the fixed corner: no launcher, open from the start, no close button. The height is 240 to 1200 pixels, 480 by default.
Each shortcode is a chat of its own. Two on a page do not share a conversation, and the floating launcher — if you left it on — is a third. All of them use the same provider, key, agent and system prompt.
The shortcode respects Who may use it and renders nothing at all when the plugin is not configured or the widget is switched off. A shortcode left behind in an old post will not turn into an error message on your public site.
Scripts and styles are only loaded where a chat is actually rendered, so a page with no chat on it loads neither the widget nor its two libraries.
7. Appearance
| Setting | Notes |
|---|---|
| Assistant name | shown in the panel header; "Assistant" when empty |
| Greeting | shown when the chat opens. Never sent to the provider, so it is free |
| AI notice | the line under every chat saying that this is an AI. Rewordable, not removable. See below |
| Starter questions | up to four, one per line. See below |
| Colour scheme | light, dark, or follow the visitor's system setting |
| Accent colour | launcher, header and visitor bubbles |
| Launcher icon | ten built-in line icons, no image files |
| Launcher corner | bottom right or bottom left, for sites where something already lives there |
| Distance from the edge | 0 to 200 pixels |
| Avatars | a small avatar beside each message |
| Small screens | hide the floating launcher below 600px wide |
Starter questions are buttons in the empty chat. They are sent only when a visitor clicks one, so they cost nothing until then, and they are the cheapest way to raise the share of visitors who ask anything at all — an empty box asks someone to think of something, three buttons do not. Pick questions your agent answers well.
AI notice is a short line between the conversation and the input field, on every chat — floating and shortcode alike. By default it reads "AI assistant — replies are generated automatically and may contain mistakes. Please check important information." You can replace it with your own wording of up to 200 characters, plain text, for example in your site's language; an empty field brings the default back. There is no switch and no filter that removes it. Article 50(1) of the EU AI Act requires that people can tell they are talking to an AI, and as the site that deploys the chat, that obligation reaches you too. The second half of the sentence — that replies can be wrong — is the honest expectation to set for any language model, and it is in your interest as well.
Avatars are drawn in the browser from your launcher icon and a generic visitor symbol. No avatar service is contacted, so this adds no outbound request and tells no third party that the page was viewed. That is why they are shapes rather than pictures of people.
Hiding on small screens applies to the floating launcher only. A chat placed by shortcode is part of the content and stays visible.
8. Things that go wrong
The Save button does nothing. Fixed in 2.0.1. If you are on an older build, update; the cause was a hidden field the browser considered invalid, which makes a browser refuse to submit without showing any message.
Replies stop mid-sentence. Raise Maximum reply length. If it persists on a KnowScapes key, the service-side cap is lower — see section 4.
"This page has expired." A page served from a cache older than the nonce lifetime carries a dead one. The widget fetches a fresh nonce and retries once by itself, so you should not see this; if you do, reloading fixes it.
The rate limit counts all visitors as one. You are behind a proxy. See the filter in section 4.
Nothing appears on my home page. Check Where the launcher appears. With the shortcode-only choice there is no launcher by design, and with the content-types choice the home page is usually not a single entry. The status panel at the top of the settings screen now says which of these applies.
The provider rejected something and I cannot see why. The chat only ever
tells visitors that the assistant is unavailable, so nothing about your
configuration leaks to them. The provider's own wording is shown to you when you
press Load available entries, and with WP_DEBUG and WP_DEBUG_LOG both
on it is written to the debug log. The API key is never in either.
9. What is stored on your site
One option row, knowscapes_ai_client_settings, and a handful of short-lived
transients: the rate-limit counters, the daily counter, a 60-second lock on the
key-request button, and the two self-expiring admin notices. No conversations, no
cookies, no visitor identifiers, no custom tables.
Uninstalling removes the option and every transient, across every site of a multisite network. Data flow and privacy has the full list with lifetimes.
Frequently asked questions
Do I need a paid account anywhere?
No. KnowScapes issues a free Community key, which is enough to run the widget. If you would rather use OpenAI, Anthropic or OpenRouter, you use your own key there and are billed by them.
I have a paid KnowScapes key. Which provider do I select?
KnowScapes (paid plan). It is technically identical to the free entry — same service, same address, same agents — but it is a separate entry with a key slot of its own, so a paid key is not pasted into a field labelled as the fair-use tier, and switching between the two does not overwrite either key. The free-key request form only appears while the free entry is selected.
What happens when the Community plan's allowance is reached?
The KnowScapes service declines further messages until the next period, and visitors see a short notice that the assistant is unavailable for now. The plugin itself is unaffected: nothing is disabled, no setting is lost, and the widget resumes on its own once the allowance is back. The reason, and a link to the current figures, appear on the plugin's settings screen while it lasts.
If the allowance is too small for your site, enter your own OpenAI, Anthropic or OpenRouter key instead — the plugin works the same way with any of them.
A reply stops in the middle of a sentence
The reply ran into Maximum reply length on the settings screen. That setting is the room a single answer gets; when a model reaches it, it stops wherever it happens to be, which reads as the assistant breaking mid-thought. Raise it — the default is 4096 tokens, roughly 3,000 words, and up to 32768 is accepted. Only the tokens actually used are billed, so a generous limit costs nothing by itself.
The plugin now notices this rather than leaving you to spot it by reading the chat: the reply carries a short neutral line telling the visitor it was cut short and that they can ask for the rest, and the settings screen shows which limit was hit and suggests a higher one. That notice clears itself once the limit is raised.
How do I put the chat inside a page instead of in the corner?
Use the shortcode [knowscapes_ai_chat] in a post, a page, a block or a
template. Add a height if the default of 480 pixels does not suit:
[knowscapes_ai_chat height="600"]. Each shortcode is a chat of its own, so two
on one page do not share a conversation, and the floating launcher — if you leave
it switched on — is a third. To have the chat only where you place it, set Where
the launcher appears to the shortcode-only option.
Can I keep the chat off certain pages?
Three ways, and they combine. Where the launcher appears can restrict it to the content types you choose, which means single entries of those types — archives, the blog index and search results are not single entries and never match. Never on these entries takes a list of post or page IDs and suppresses the launcher there whatever the rule above says, which is what you want for a checkout or a contact page. And the launcher can be hidden on screens narrower than 600 pixels, for sites where a chat panel would cover the page on a phone. A shortcode placed inside an entry always works, exclusions included — placing it there is itself the decision.
What does page context actually send?
With page context on, the title, address and plain text of the page the visitor
is reading are put into your system prompt wherever you wrote {title}, {url}
or {content}, up to the character limit you set, and travel to the provider
with each message. {site_name} is also available.
Only fully published, public content is ever read. A draft, a scheduled or pending post, a private one, a password-protected one, a revision, an autosave and anything of a post type that is not publicly queryable are all refused, and the text is taken from the stored content with shortcodes stripped rather than executed. This matters because the page is identified by an ID that comes from the visitor's browser, so it has to be treated as something anyone can change: the checks are what stop the assistant being asked about post 123 and answering out of an unpublished draft.
It is off by default, because it changes what leaves your site. Switching it on also adds a paragraph to the privacy policy text the plugin suggests under Tools → Privacy.
Can I keep the assistant for my own team?
Set Who may use it to signed-in users only. That both hides the chat and refuses the request behind it, so it is not bypassed by someone who knows the REST route. Signed-in users also get their own rate limit, counted per account rather than per address — which is fairer as well as more accurate, since colleagues behind one office router no longer share a single budget.
Why do so few visitors send a message?
An empty box asks the visitor to think of something. Starter questions give them two to four buttons instead; they cost nothing until one is clicked, and they also steer people towards what your agent answers well. Set them under Appearance, one per line.
Can I remove the "AI assistant" line under the chat?
You can reword it, not remove it. The field is AI notice under Appearance; leave it empty for the built-in text, or write your own — in your site's language, for example. Article 50 of the EU AI Act requires that people can tell they are talking to an AI, and the same line tells them that replies can be wrong, which protects you as well. If you build your own front end on the REST route instead of using this widget, show an equivalent notice there.
Can visitors see my API key?
No. The browser only ever talks to a REST route on your own site. The key is added server-side and never appears in any page, script or response.
Is any visitor data stored?
The plugin stores no conversations, no cookies and no visitor identifiers. A conversation exists only in the visitor's browser and is gone when the page is closed.
That covers this plugin and your site. It says nothing about how long your chosen provider keeps the messages you transmit to them — that is set by their terms and privacy policy, it differs between providers, and the plugin has no way to know it. Read the policy of the provider you selected before you describe retention in your own privacy policy; the links are next to the consent box on the settings screen. The visitor's IP address is hashed into a short-lived counter purely to enforce the rate limit, and that counter expires after two minutes. The address itself is never written anywhere.
The chat says the page has expired
The request is protected by a WordPress nonce, and nonces have a limited lifetime, so a page served from a cache older than that carries a dead one. The widget detects this, fetches a fresh nonce and retries the message once by itself, so in practice you should not see this. If you do, reloading the page always fixes it.
The rate limit counts all my visitors as one
Visitors are bucketed by the address your web server reports. Forwarded-for
headers are deliberately not trusted, because anyone can send one and the limit
would be trivial to bypass. Behind a CDN or a reverse proxy, though, that address
is the proxy, so every visitor shares one budget. If you trust your proxy, supply
the real address with the knowscapes_ai_client_client_ip filter:
add_filter( 'knowscapes_ai_client_client_ip', function () { return $_SERVER['HTTP_CF_CONNECTING_IP'] ?? $_SERVER['REMOTE_ADDR']; } );
How do I stop the chat running up a bill?
Two limits work together. The per-visitor limit caps how fast one address can send, and the daily ceiling caps how many messages the whole site will answer in a day, which is the one that matters under abuse because an attacker can change address. Both are on the settings screen; the daily ceiling defaults to 500 and can be set to 0 to remove it. Only messages that actually reach the provider are counted, so malformed requests cannot exhaust your budget.
The daily counter is best-effort rather than exact: WordPress offers no atomic counter that works both with and without a persistent object cache, so a burst of truly simultaneous requests can slip a few past the ceiling. It undercounts, never over, so it will not lock out your visitors early. Treat it as a guardrail against runaway usage, and set a hard spending limit at your provider as well if the bill matters to you.
Where do I see why the provider rejected something?
The chat itself only ever tells visitors that the assistant is unavailable, so
nothing about your configuration leaks to them. The provider's own wording is
shown to you when you press Load available entries on the settings screen,
and, with WP_DEBUG and WP_DEBUG_LOG both enabled, is written to the WordPress
debug log. The API key is never included in either.
Why does the reply appear all at once instead of word by word?
This version fetches the complete reply and then reveals it progressively in the browser, which keeps the whole transport on the WordPress HTTP API and needs no direct socket handling. A token-by-token streaming transport is planned for a later release.
Which third-party libraries are bundled?
Two, both GPL-compatible, both shipped unminified, both served locally and never from a CDN:
- marked 15.0.12 (MIT) — https://github.com/markedjs/marked
- DOMPurify 3.4.16 (Apache-2.0 / MPL-2.0) — https://github.com/cure53/DOMPurify
Nothing in this package is minified and no build step runs over the plugin's own code: every line that runs is a line you can read. Both libraries are the browser distribution each project publishes, unmodified apart from a trailing source-map comment removed because the map files are not shipped. marked is pinned to 15.0.12 rather than the newest release because from version 16 on it publishes only a bundled, minified browser build. The plugin's own source is at https://githel.xplicator.com/.
Data flow and privacy
Every request that leaves a site running this plugin, everything it stores, and what triggers each. For version 2.3.0.
Two audiences, and the same facts serve both: a site owner writing a privacy
policy, and a wordpress.org reviewer checking guideline 7 (no tracking without
consent) and guideline 6 (service dependencies disclosed). The plugin's
readme.txt carries a condensed version of section 2 under "External services",
which is the part the Plugin Directory requires.
1. The short version
On a fresh install the plugin sends nothing anywhere. It has no telemetry, no update pings, no analytics, no CDN, no remote fonts, no avatar service, and no "phone home" of any kind — not on activation, not on a schedule, not in the background.
Three kinds of outbound request exist, and every one of them follows a deliberate action:
- A visitor sends a chat message.
- The administrator enters an API key or presses Load available entries.
- The administrator presses Request free key.
The first requires the administrator to have ticked a consent box naming the provider. Until then the widget is not rendered and its scripts are not even enqueued.
The plugin stores no conversations, sets no cookies, and writes no visitor identifier anywhere.
2. Outbound requests
2.1 A visitor's chat message
Trigger: a visitor submits a message in the chat. Precondition: a provider and key are configured, a model is chosen, the consent box is ticked, the widget is switched on, and — in signed-in-only mode — the visitor is signed in. Destination: the selected provider. One of:
| Provider | Endpoint |
|---|---|
| KnowScapes, free or paid | https://knowscapes.com/api/v1/chat/completions |
| OpenAI | https://api.openai.com/v1/chat/completions |
| Anthropic | https://api.anthropic.com/v1/messages |
| OpenRouter | https://openrouter.ai/api/v1/chat/completions |
| Custom | the https base URL the administrator entered, + /v1/chat/completions |
What is sent:
- the text of the visitor's current message;
- the earlier messages of that same conversation, at most the last 20, each truncated to 4000 characters;
- the system prompt the administrator configured, if any;
- with page context switched on, whatever the system prompt's placeholders resolve to: the title, permalink and plain text of the published page the visitor is reading, the text capped at the administrator's limit (200–20000 characters, 4000 by default);
- the model or agent identifier;
- the reply-length cap;
- the API key, as an authorisation header.
What is not sent: the visitor's IP address, user agent, referrer, user ID, name or e-mail; any cookie; any page other than the one being read; any identifier the plugin invented. The request is made server-to-server by WordPress, so the provider does not see the visitor's browser at all.
Important for a reviewer: the visitor's browser never contacts the provider. It posts to a REST route on the site's own domain, and only PHP on that server talks outward. That is why the API key can stay server-side.
2.2 The model list
Trigger: the administrator enters an API key on the settings screen (after a
paste, on leaving the field, or after a pause in typing), or presses Load
available entries. Never on page load, and never in the background.
Destination: GET <provider base>/v1/models.
What is sent: the API key as an authorisation header, and nothing else. No
site data.
Who can do it: manage_options only, through a REST route that checks that
capability.
2.3 The free-key request
Trigger: the administrator fills in the form, ticks the confirmation box and
presses Request free key. Never automatic. Behind manage_options and a
WordPress nonce.
Destination: POST https://knowscapes.com/api/trial-keys.
The path reads trial-keys because that is the route's name on the service. The
tier it issues is a free one with a fair-use allowance, not a time-limited
trial; nothing in the plugin expires, and no feature is withheld from a site
that never requests a key.
What is sent:
{
"email": "the address the administrator typed",
"site_url": "https://example.com/",
"site_name": "Example",
"claim_url": "https://example.com/wp-admin/admin.php?page=knowscapes-ai-client"
}
claim_url is what allows the confirmation e-mail to carry a button that offers
to install the key on this site.
What comes back: a confirmation that the key was sent. The key itself arrives by e-mail and is never in the response; if a response did contain one, the plugin would ignore it.
What is stored locally: one flag that expires after 60 seconds, which stops the button being pressed repeatedly. Neither the e-mail address nor the key is kept as a result of the request.
About the install link. The link in that e-mail carries the key as part of its
address, so it can appear in a server access log, and anyone holding the link
could offer the key to any site. Opening it stores nothing: it renders a
confirmation screen naming the key, and only a submitted form — with a nonce and
manage_options — writes it. A mail scanner or link prefetcher that fetches the
link therefore changes nothing. The screen says to confirm only a key the
administrator requested themselves.
2.4 Nothing else
There is no fourth. In particular:
- No CDN. Both bundled libraries (
marked,DOMPurify) are served from the plugin directory. - No web fonts. The widget uses the visitor's system font stack.
- No avatar service. Avatars, when switched on, are SVG shapes drawn in the browser from the launcher icon and a generic symbol — deliberately not Gravatar, which would mean a request per page view to a third party.
- No images loaded from anywhere. Every icon is inline SVG.
- No update or licence check. The plugin never asks any server whether it may run.
3. What is stored on the site
3.1 The option
One row in wp_options, knowscapes_ai_client_settings, holding the
configuration listed in Developer reference, section 3. It contains the API
keys, which is why they never reach the browser.
3.2 Transients
| Name | Contents | Lifetime |
|---|---|---|
knowscapes_ai_client_rl_<bucket>_<minute> |
a small integer | 2 minutes |
knowscapes_ai_client_rl_day_<Ymd> |
a small integer | 2 days |
knowscapes_ai_client_request_throttle |
1 |
60 seconds |
knowscapes_ai_client_quota_notice |
the provider's own wording and link | the Retry-After it asked for, capped at a week |
knowscapes_ai_client_truncation_notice |
provider slug, the cap that was hit, a timestamp | a week, or until the cap is raised |
<bucket> is md5( <address> ) for an anonymous visitor and u<user id> for a
signed-in one. The plain address is used only to compute that hash, in memory,
and is never written. The counters hold a number and nothing else — no addresses,
no message text, no timestamps per visitor.
3.3 What is not stored
No conversations. No cookies, of any kind, ever. No custom database tables. No log of who asked what. No visitor identifier the plugin invented. A conversation exists only in the visitor's browser, in JavaScript variables, and is gone when the page is closed or reloaded.
There is also no browser storage: no localStorage, no sessionStorage, no
IndexedDB.
3.4 Uninstalling
uninstall.php deletes the option and every transient whose name begins with
_transient_knowscapes_ai_client_, for each site of a multisite network. Nothing
is left behind.
4. Page context, specifically
This is the one feature that sends something other than what a visitor typed, so it deserves its own account.
It is off by default, and switching it on is what makes the placeholders in the system prompt work at all.
With it on, the page the visitor is reading is identified by a post ID that the plugin printed into that page and the browser sends back. That ID is attacker-controlled: anyone can post a different number to the REST route. Every check below exists because of that, not out of caution about honest visitors.
Refused, always:
- anything whose status is not exactly
publish— draft, pending, future, private, trashed, auto-draft; - revisions and autosaves, which share their parent's status and would otherwise expose the text of an unsaved edit;
- password-protected entries, unless the visitor has already entered the password;
- post types that are not registered as
public; - IDs that are zero, negative or unknown.
What is read is the stored post_content with shortcodes stripped rather than
executed and editor block comments removed, converted to plain text and cut to
the configured limit. Since only published public content passes, everything sent
is already readable by anyone who visits that URL.
When it is on, the privacy-policy text the plugin suggests under Tools → Privacy gains a paragraph saying so. With it off, that paragraph is absent — the suggestion describes what the site actually does, not what the plugin could do.
5. Consent, and what it gates
The data-transfer checkbox names the selected provider and links to that provider's terms and privacy policy. Until it is ticked:
widget_enabledcannot be true — the sanitiser refuses the combination;- the widget is not rendered;
- its scripts and styles are not enqueued, so a front-end page loads nothing from the plugin at all;
knowscapes_ai_client_is_ready()is false, so the/chatand/nonceroutes return 503.
The two administrator-triggered requests (2.2 and 2.3) do not depend on that checkbox, because each has its own explicit action — and the key-request form has a separate confirmation box of its own naming what is transmitted. Neither sends any visitor data.
6. GDPR notes for the site owner
Not legal advice; the plugin author is not a lawyer. What the plugin gives you to work with:
- Suggested policy text under Tools → Privacy, which names the transfer and the provider, and covers page context when it is on.
- A processor relationship with whichever provider you selected. Visitor messages are personal data as soon as a visitor types something identifying into them, which you cannot prevent. That makes your provider a processor, and a data processing agreement with them your responsibility. Their terms and privacy links are on the settings screen next to the consent box.
- Nothing to export or erase from the plugin. There is no subject access request the plugin can answer, because it stores nothing about a visitor: no conversation log, no identifier, no cookie. That is also why it registers no WordPress privacy exporter or eraser — there would be nothing for them to return. If a future version adds conversation logging, that changes, and it would need both hooks.
- Transfers outside the EU. KnowScapes is operated by xplicator GmbH in Hamburg. OpenAI, Anthropic and OpenRouter are US companies; if you select one of them, visitor messages leave the EU. That belongs in your policy. For KnowScapes, processing location, retention and sub-processors are set out in the service description at https://xplicator.com/legal-service-knowscapes/, and a data processing agreement is available from xplicator GmbH on request.
- An AI notice. Article 50(1) of the EU AI Act requires that people can tell they are interacting with an AI. From 2.3.0 every chat carries a short notice saying so, and that replies may contain mistakes. You can reword it under Appearance → AI notice but not remove it. If you build your own front end on the plugin's REST route, show an equivalent notice there.
If your site needs a cookie or consent banner before the assistant may be used at all, the plugin does not provide one — but two settings get you most of the way: place the chat by shortcode only, so it appears where you decide, and restrict it to signed-in users if that fits your situation.
External services
This plugin is a client for external AI services. Which service is contacted depends on the provider you select in its settings. Nothing below happens on a default installation; each transfer requires the configuration steps described above.
KnowScapes AI API (xplicator GmbH)
Used when either KnowScapes Community (free key) or KnowScapes (paid plan) is selected. Both reach the same service at the same address; they differ only in which key is sent, and therefore in the allowance that applies to it.
- What is sent: the text of the visitor's current message, the earlier messages of that same conversation (at most the last 20, each truncated to 4,000 characters), your configured system prompt, and the agent identifier. If you switch page context on, your system prompt can also carry the title, address and text of the published page the visitor is reading, up to the character limit you set. The API key is sent as an authorisation header.
- When: each time a visitor submits a message in the chat.
- Endpoints:
https://knowscapes.com/api/v1/chat/completionsand, when you press the button to load the agent list,https://knowscapes.com/api/v1/models. - Terms of use: https://xplicator.com/legal-terms-of-use/
- Privacy policy: https://xplicator.com/legal-service-privacy/
- Service description (processing location, retention, sub-processors): https://xplicator.com/legal-service-knowscapes/
KnowScapes free key service (xplicator GmbH)
Used only when you fill in the request form on the settings screen, tick the consent box and press the button. It is never called automatically.
- What is sent: the e-mail address you entered; the URL and the name of this site; and the address of this plugin's own settings page inside your WordPress admin area. The settings-page address is what allows the confirmation e-mail to carry a button that offers to install the key on this site.
- When: once, on that button press.
- Endpoint:
https://knowscapes.com/api/trial-keys— the path is the service's own name for its route; the key it issues is a free one with a fair-use allowance, not a time-limited trial. - What comes back: a confirmation that the key was sent. The key itself arrives by e-mail and is never contained in the response.
- Stored locally: only a flag that expires after 60 seconds, which stops the button being pressed repeatedly. Neither your e-mail address nor the key is kept.
- Note on the install link: the link in that e-mail carries the key as part of its address, so it can appear in your server's access log, and anyone holding the link could offer the key to any site. Opening it stores nothing — it shows a confirmation screen naming the key, and only your confirmation stores it. Confirm only a key you requested yourself, or ignore the link and paste the key from the e-mail into the API key field by hand.
- Terms of use: https://xplicator.com/legal-terms-of-use/
- Privacy policy: https://xplicator.com/legal-service-privacy/
OpenAI
Used when the provider OpenAI is selected, with your own key.
- What is sent: the conversation and system prompt as described above — including the page context, if you switched it on — plus the model name, authorised with your key.
- When: each time a visitor submits a message, and when you load the model list.
- Endpoints:
https://api.openai.com/v1/chat/completions,https://api.openai.com/v1/models - Terms of use: https://openai.com/policies/terms-of-use/
- Privacy policy: https://openai.com/policies/privacy-policy/
Anthropic
Used when the provider Anthropic is selected, with your own key.
- What is sent: the conversation and system prompt as described above, plus the model name, authorised with your key.
- When: each time a visitor submits a message, and when you load the model list.
- Endpoints:
https://api.anthropic.com/v1/messages,https://api.anthropic.com/v1/models - Terms of use: https://www.anthropic.com/legal/commercial-terms
- Privacy policy: https://www.anthropic.com/legal/privacy
OpenRouter
Used when the provider OpenRouter is selected, with your own key.
- What is sent: the conversation and system prompt as described above, plus the model name, authorised with your key.
- When: each time a visitor submits a message, and when you load the model list.
- Endpoints:
https://openrouter.ai/api/v1/chat/completions,https://openrouter.ai/api/v1/models - Terms of use: https://openrouter.ai/terms
- Privacy policy: https://openrouter.ai/privacy
Custom endpoint
When you select the custom provider, requests go to the HTTPS base URL you entered and to no one else. You are responsible for the terms that apply there.
Developer reference
The plugin's own surface: files, option shape, REST routes, hooks, classes and front-end contract. For version 2.3.0.
Nothing here is a promise of stability yet. The plugin has one documented filter; everything else is internal and may change between minor versions. If you are building against something in here that is not in section 5, say so and it can be given a hook instead.
1. Layout
knowscapes-ai-client.php bootstrap: constants, defaults, upgrade, privacy text
uninstall.php removes the option and every transient, network-wide
readme.txt the wordpress.org readme
includes/
class-providers.php provider registry
class-api-client.php outbound transport, both dialects
class-context.php page-context placeholders
class-free-key.php free-key request and claim
class-settings.php admin screen, sanitiser
class-rest-proxy.php /chat, /nonce, /models
class-widget.php front-end output and the shortcode
assets/
css/admin.css css/widget.css
js/admin.js js/widget.js
vendor/marked.js vendor/purify.js (+ licences, README)
Not shipped (listed in .distignore): doc/, dev/, dist/,
build-dist.sh.
Everything runs unminified and unbundled. There is no build step over the
plugin's own code; build-dist.sh only copies and zips.
2. Constants
| Constant | Value in 2.3.0 |
|---|---|
KNOWSCAPES_AI_CLIENT_VERSION |
2.3.0 |
KNOWSCAPES_AI_CLIENT_DB_VERSION |
3 |
KNOWSCAPES_AI_CLIENT_OPTION |
knowscapes_ai_client_settings |
KNOWSCAPES_AI_CLIENT_FILE / _DIR / _URL |
plugin file, path, URL |
KNOWSCAPES_AI_CLIENT_DEFAULT_MAX_TOKENS |
4096 |
KNOWSCAPES_AI_CLIENT_MIN_MAX_TOKENS |
64 |
KNOWSCAPES_AI_CLIENT_MAX_MAX_TOKENS |
32768 |
KNOWSCAPES_AI_CLIENT_AI_NOTICE_MAX |
200 (characters) |
Class constants worth knowing: KnowScapes_AI_Client_REST_Proxy::REST_NS
(knowscapes-ai-client/v1), ::MAX_MESSAGES (20), ::MAX_CHARS (4000);
KnowScapes_AI_Client_Widget::SHORTCODE (knowscapes_ai_chat, used as
[knowscapes_ai_chat]);
KnowScapes_AI_Client_Settings::MENU_SLUG, ::OPTION_GROUP;
KnowScapes_AI_Client_API_Client::TIMEOUT (60 seconds).
3. The option
One row, knowscapes_ai_client_settings, an array. Read it with
knowscapes_ai_client_get_settings(), never with get_option() directly: the
accessor fills in keys added by later versions and guarantees that every
list-valued key is a list.
| Key | Type | Default | Range / values |
|---|---|---|---|
db_version |
int | 3 |
schema version, managed by the upgrade routine |
provider |
string | knowscapes |
a key of Providers::all() |
api_keys |
array | [] |
provider slug ⇒ key |
custom_base_url |
string | '' |
https only, no trailing slash |
model |
string | '' |
free text |
model_label |
string | '' |
display label for model |
system_prompt |
string | '' |
may contain the placeholders in section 6 |
max_tokens |
int | 4096 |
64 – 32768 |
bot_name |
string | '' |
falls back to "Assistant" |
initial_message |
string | '' |
never sent to the provider |
ai_notice |
string | '' |
wording of the AI notice; '' = built-in text. Plain text, up to 200 characters. The notice itself is always shown |
starters |
array | [] |
up to 4 strings |
theme |
string | auto |
auto | light | dark |
accent |
string | #4f46e5 |
hex colour |
launcher_icon |
string | sparkles |
a key of Settings::launcher_icons() |
launcher_side |
string | right |
right | left |
launcher_offset |
int | 20 |
0 – 200 (pixels) |
show_avatars |
bool | false |
|
hide_on_mobile |
bool | false |
hides the launcher below 600px |
show_think |
bool | false |
reveal <think> blocks |
placement |
string | everywhere |
everywhere | post_types | shortcode |
placement_types |
array | [] |
public post type slugs |
exclude_ids |
array | [] |
up to 200 post IDs |
context_enabled |
bool | false |
page context master switch |
context_chars |
int | 4000 |
200 – 20000 |
access |
string | everyone |
everyone | logged_in |
rate_per_min |
int | 10 |
1 – 120, per address |
rate_per_min_users |
int | 30 |
1 – 600, per signed-in account |
rate_per_day |
int | 500 |
0 – 100000, 0 = no ceiling |
consent |
bool | false |
nothing outbound without it |
widget_enabled |
bool | false |
cannot be true without consent |
Writing to it
register_setting() attaches the sanitiser to sanitize_option_knowscapes_ai_client_settings,
and update_option() runs that filter on every write, not only on posts from
options.php. So KnowScapes_AI_Client_Settings::sanitize() accepts two shapes:
- a posted form, with a single
api_keyplus akey_providerhidden field and noapi_keysarray; - a complete settings array written programmatically, with
api_keysand noapi_key.
If you write the option from your own code, pass a complete array and keep
api_keys in it. A partial array will not merge — the keys you leave out fall
back to what is stored, and anything the sanitiser treats as "absent" is reset to
its default.
Upgrades
knowscapes_ai_client_maybe_upgrade() runs on plugins_loaded at priority 5
and on activation. Both, because WordPress does not fire activation hooks when
a plugin is updated — an upgrade routine that only ran on activation would never
reach the installations that most need it.
| To | What it does |
|---|---|
| 2 | flat api_key/backend_url become the per-provider api_keys map; the shared key and agent that versions up to 1.0.1 shipped as defaults are discarded by SHA-256 digest; consent is reset |
| 3 | a max_tokens still equal to the old default of 1024 is raised to 4096; any other value is left alone |
4. REST routes
Namespace knowscapes-ai-client/v1.
POST /chat
The only route a visitor's browser uses. Requires an X-WP-Nonce header for the
wp_rest action.
{ "messages": [ { "role": "user", "content": "…" } ], "post_id": 123 }
messages is required; post_id is optional and is only a pointer — the server
decides whether that entry may be read at all (section 6).
{ "role": "assistant", "content": "…", "truncated": false }
truncated is true when the model stopped because it ran out of room rather than
because it finished.
Permission callback, in order: nonce; configured-and-ready; access mode; the
per-visitor or per-account minute limit; the site-wide daily ceiling. The daily
ceiling is only checked there and charged in the handler after the payload
validates — a counter charged in a permission callback would let anyone exhaust
a whole site's budget for a day with cheap malformed requests.
Message normalisation, in sanitize_messages(): at most 20 messages, each
truncated to 4000 characters with mb_substr; only the roles user and
assistant survive; C0 control characters are stripped; a leading assistant
turn is dropped and adjacent same-role turns are merged, because the Anthropic
Messages API rejects both; the last turn must be user.
There is deliberately no tag stripping. strip_tags() discards everything
from a < to the next >, which would turn "I \<3 your shop" into "I " and
swallow any question about HTML. The text is JSON-encoded on the way out and the
reply is sanitised with DOMPurify in the browser.
GET /nonce
Public, read-only, permission_callback is __return_true. Hands back a fresh
wp_rest nonce so a visitor on a page cached beyond the nonce lifetime can retry
instead of being told the page expired. The value is not a secret — it is printed
into every front-end page anyway, and for logged-out visitors it is identical for
everyone. Returns 503 while the plugin is not ready, and sends
Cache-Control: no-store.
POST /models
manage_options only. Optional provider, key and base_url parameters, so
the settings screen can probe a key before it is saved; falls back to the stored
configuration. Returns { "models": [ { "id": "…", "label": "…" } ] }, or
{ "error": "…" } with status 400. This is the one place the provider's own
error wording is shown in the interface, because it is administrators only and it
is usually what reveals a wrong endpoint or a plan without access.
Error codes
All are WP_Error codes; the widget keys off the code string, not the message.
| Code | Status | Meaning |
|---|---|---|
knowscapes_ai_client_nonce |
403 | missing or stale nonce |
knowscapes_ai_client_forbidden |
403 | signed-in-only mode, visitor is not |
knowscapes_ai_client_config |
503 | not configured or not ready |
knowscapes_ai_client_bad_request |
400 | no usable message in the payload |
knowscapes_ai_client_rate_limited |
429 | too fast — retrying shortly will work |
knowscapes_ai_client_daily_limit |
429 | site-wide daily ceiling reached |
knowscapes_ai_client_quota_exhausted |
429 | the provider says the allowance is used up |
knowscapes_ai_client_transport |
502 | the provider could not be reached |
knowscapes_ai_client_upstream |
502 | the provider returned a non-200 |
knowscapes_ai_client_empty |
502 | the provider returned an empty reply |
Two more come from get_models() and so only ever reach POST /models, where
the administrator is the audience:
| Code | Meaning |
|---|---|
knowscapes_ai_client_auth |
the provider rejected the key |
knowscapes_ai_client_http |
a non-200 from the model list, with the provider's own wording attached |
The two 429s are kept apart on purpose. "Slow down" is worth telling a visitor to retry; an allowance that ran out is not, and is not their business either, so it gets a neutral line while the detail goes to the admin screen.
5. Hooks
knowscapes_ai_client_client_ip (filter)
The address the rate limiter buckets an anonymous visitor into.
add_filter( 'knowscapes_ai_client_client_ip', function ( $ip ) {
return $_SERVER['HTTP_CF_CONNECTING_IP'] ?? $ip;
} );
Forwarded-for headers are not read by default because anyone can send them, which would make the limit trivial to bypass. The consequence is that behind a proxy every visitor shares one bucket; this filter is how a site owner who trusts their proxy fixes that. Signed-in visitors do not go through it — they are bucketed by user ID.
That is the only filter the plugin defines. It also uses three WordPress hooks
you may care about: wp_add_privacy_policy_content on admin_init,
plugin_action_links_<basename> for the Settings link, and the standard
register_setting() machinery under the option group
knowscapes_ai_client_group.
6. Page context
KnowScapes_AI_Client_Context::resolve( $system_prompt, $post_id, $settings )
returns the prompt with {title}, {url}, {site_name} and {content}
replaced. A prompt containing none of them is returned untouched, so the class
costs nothing when unused.
The $post_id comes from the browser. readable_post() refuses everything that
is not fully public:
(int)rather thanabsint(), then<= 0—absint( -100 )is 100, which would quietly turn a nonsensical id into a real post;get_post()must return aWP_Post;get_post_status()must be exactlypublish— that excludes draft, pending, future, private, trashed and auto-draft;wp_is_post_revision()andwp_is_post_autosave()must both be false, because a revision shares its parent's status and status alone would let the text of an unsaved edit through;post_password_required()must be false;- the post type must exist and be
public.
plain_content() uses the stored post_content rather than the output of the
the_content filters: those run the whole theme and plugin rendering stack,
which is expensive on a visitor's request and can have side effects that have no
business happening inside a REST call. Shortcodes are stripped rather than
executed for the same reason. Editor block comments are removed, block-level tags
become newlines before tags are dropped, entities are decoded, runs of blank
lines collapse, and the result is cut with mb_substr so the cut never lands
inside a multi-byte character — an invalid tail would make wp_json_encode()
drop the whole field and the request would go out with no system prompt at all.
7. Providers
KnowScapes_AI_Client_Providers::all() returns slug ⇒ definition:
| Field | Meaning |
|---|---|
label |
translated name shown in the dropdown |
dialect |
openai (POST /v1/chat/completions) or anthropic (POST /v1/messages) |
base_url |
fixed endpoint, or '' for the custom provider |
key_url |
where to get a key, or '' for none |
terms_url, privacy_url |
linked from the consent block |
model_label |
"Model" or "Agent" |
key_hint |
one line under the key field |
free_tier |
whether the free-key request form applies |
base_url( $slug, $custom ) resolves and validates: https only, host required,
no trailing slash, '' when unusable. knowscapes_ai_client_is_ready() treats
an empty result as not-configured, so a bad custom endpoint disables the widget
rather than sending a key over plain http.
To add a provider, add an entry here. Nothing else is provider-specific: the
transport picks its dialect and headers from dialect, the settings screen
builds its dropdown, hint and links from the definition, and admin.js receives
the whole registry through wp_localize_script.
knowscapes and knowscapes_paid are the same endpoint in the same dialect.
They are two entries because the keys are different credentials with different
allowances and each entry keeps its own key slot.
8. Transport
KnowScapes_AI_Client_API_Client uses wp_remote_post/wp_remote_get only, so
site owners keep the usual filters over outbound requests. Nothing is streamed;
the reply arrives whole and the widget reveals it progressively.
chat( $model, $messages, $system_prompt = '', $max_tokens = 0 )— a$max_tokensbelow the floor means "not configured" and takes the default, not the floor: clamping a missing value to 64 would cut every reply off after two sentences.- Reasoning models on the OpenAI-compatible route reject
max_tokensand wantmax_completion_tokens. Which applies depends on the model, and the model is free text, so a 400 mentioning that field triggers one retry with the parameter swapped. was_truncated()— true when the last reply hit the cap. Read fromchoices[0].finish_reason == "length"orstop_reason == "max_tokens"; both spellings are accepted whatever the dialect, because gateways are inconsistent about which they pass through. Also stores the self-expiring admin notice.get_models()readsGET /v1/modelsand sorts by label.log_upstream()writes only whileWP_DEBUGandWP_DEBUG_LOGare both on, and never includes the key.
9. Front end
Mount points
The script builds one chat per element with the class ksaic-mount:
<div id="knowscapes-ai-client-root" class="ksaic-root ksaic-mount ksaic-side-right"
style="--ksaic-offset:20px"></div>
<div class="ksaic-root ksaic-root-inline ksaic-mount" data-inline="1"
style="--ksaic-inline-height:480px"></div>
data-inline means no launcher, open from the start, no close button, and a
panel in the flow of the page rather than fixed. Per-instance state lives inside
one boot() call, so two chats never share a conversation; only the stateless
helpers and the refreshed nonce are shared.
The id on the floating mount is kept for anyone who styled against it, but the
script finds mounts by class.
Configuration
window.knowscapesAIClientCfg, printed by wp_localize_script. It carries
restUrl, nonceUrl, nonce, botName, initialMessage, aiNotice, theme, accent,
showThink, showAvatars, launcherIcon, starters, postId and an i18n
map. No key material, no provider endpoint and no model name are ever in it —
there is a test for each.
AI notice
Every mount gets a <p class="ksaic-ai-notice"> between the log and the input
form, with a per-instance id that the panel references via
aria-describedby. Its text comes from knowscapes_ai_client_ai_notice(),
which returns the stored ai_notice or, when that is blank,
knowscapes_ai_client_default_ai_notice(). The script falls back to the English
default if aiNotice is missing from the configuration. The stylesheet sets
display and visibility with !important so a theme rule cannot hide it by
accident. There is deliberately no filter to remove it: the notice exists to meet
Art. 50(1) of the EU AI Act. A theme may restyle it — colour, size, alignment —
as long as it stays visible.
Styling
All classes are prefixed ksaic-. Colours come from custom properties on
.ksaic-root, redefined under [data-theme="dark"], so a theme can override
them without touching the rules:
--ksaic-accent, --ksaic-bg, --ksaic-fg, --ksaic-muted, --ksaic-border,
--ksaic-user-bg, --ksaic-user-fg, --ksaic-assist-bg, --ksaic-assist-fg,
--ksaic-shadow, plus --ksaic-offset and --ksaic-inline-height.
Layout properties on .ksaic-launcher carry !important because several themes
set global button dimensions that would otherwise distort the round launcher into
an oval.
Assets
Registered on wp_enqueue_scripts whenever the plugin is configured, and
enqueued only where a chat is rendered — by maybe_enqueue_for_page() at
priority 20 for the floating launcher, and by the shortcode handler otherwise.
A shortcode runs during the_content, long after wp_enqueue_scripts, which is
why registration and enqueueing are separate steps; footer scripts are printed
later still, so it works.
marked 15.0.12 and DOMPurify 3.4.16 are served locally, unminified, never
from a CDN. marked is pinned to 15.x because from 16 on it publishes only a
bundled, minified browser build.
10. Storage
| Name | Kind | Lifetime |
|---|---|---|
knowscapes_ai_client_settings |
option | until uninstall |
knowscapes_ai_client_rl_<bucket>_<minute> |
transient | 2 minutes |
knowscapes_ai_client_rl_day_<Ymd> |
transient | 2 days |
knowscapes_ai_client_request_throttle |
transient | 60 seconds |
knowscapes_ai_client_quota_notice |
transient | the provider's Retry-After, capped at a week |
knowscapes_ai_client_truncation_notice |
transient | a week, or until the cap is raised |
<bucket> is md5( address ) for an anonymous visitor and u<user id> for a
signed-in one. The address itself is never written anywhere.
No conversations, no cookies, no visitor identifiers, no custom tables.
uninstall.php deletes the option, then every transient by name prefix, for each
site of a multisite network.
Legal documents
The KnowScapes service is operated by xplicator GmbH, Eichholz 52, 20459 Hamburg, Germany. These documents apply when you use it:
| Document | Applies to |
|---|---|
| Terms of Use | every use of the service, free or paid |
| Privacy Notice for Services | processing of content you and your visitors send to the service |
| Annex A — KnowScapes | service description: what it does, processing location, retention, sub-processors |
| General Terms and Conditions | paid plans |
| Plans and pricing | plans, prices and allowances of paid plans |
| Community plan limits | the free plan's current allowance |
| Legal notice (Impressum) | all xplicator websites |
The plugin itself is free software under the GPL, version 2 or later. Using OpenAI, Anthropic, OpenRouter or a custom endpoint is governed by that provider's own terms, linked under External services.
Changelog
Changes
2.3.0
- Added an AI notice under every chat: visitors are told that they are talking to an AI and that replies may contain mistakes. The wording can be changed under Appearance → AI notice (up to 200 characters, plain text); an empty field uses the built-in text. The notice cannot be switched off, because the EU AI Act requires that people can tell they are interacting with an AI. It is linked to the chat panel for screen readers.
- The model or agent is now picked from a dropdown filled with the provider's own list, instead of typed. The list loads by itself as soon as an API key is entered — after a paste, when the field is left, or after a short pause in typing — and the first entry is selected unless the current one is in the list. Enter manually… keeps free entry for endpoints that publish no list. Opening the settings screen still contacts no one.
- Moved the free-key request from the foot of the settings screen to the row directly under the API key field, where the key is needed. It is open while no key is stored and folds into one line once there is one. It still posts separately from the settings and only after its consent box is ticked.
- Renamed the free provider entry to KnowScapes Community (free key), after the free KnowScapes plan. Stored keys and settings are unaffected.
- The readme now links the KnowScapes service description, which states processing location, retention and sub-processors.
2.2.1
- Corrected the privacy policy text the plugin suggests under Tools → Privacy. It said conversations are held in the browser only — true of the plugin, but a site owner pasting it into their own policy would have been stating something about their chosen provider's retention that the plugin cannot know. Every claim is now scoped to the plugin, with a sentence pointing at the provider's own terms.
- The same clarification in the "Is any visitor data stored?" FAQ entry.
2.2.0
- Added the
[knowscapes_ai_chat]shortcode, so the chat can sit inside a post, a page, a block or a template instead of only in the corner. Each one is a conversation of its own, and the height is settable per shortcode. - Added placement control: the floating launcher can appear on every page, only on the content types you choose, or nowhere at all. Individual entries can be excluded by ID, and the launcher can be hidden on screens narrower than 600 pixels.
- Added starter questions — up to four buttons in the empty chat. They are sent only when a visitor clicks one, so they cost nothing until then.
- Added page context:
{title},{url},{site_name}and{content}in the system prompt are filled in from the page the visitor is reading. Off by default. Only fully published public content is ever read — drafts, scheduled, pending and private posts, password-protected posts, revisions, autosaves and non-public post types are all refused, because the page is identified by an ID that comes from the browser. - Added a signed-in-users-only mode. It hides the chat and refuses the REST request, rather than only hiding it, and signed-in users get their own rate limit counted per account instead of per address.
- Added a copy button to every reply, optional avatars, and a choice of bottom corner and distance from the edge for the launcher.
- Avatars are drawn in the browser from the launcher icon and a generic symbol. No avatar service is contacted, so this adds no outbound request and tells no third party that the page was viewed.
- The privacy policy text the plugin suggests now describes the page-context transfer, but only while page context is switched on.
- Scripts and styles are now registered up front and enqueued only where a chat is actually rendered, so a page with no chat on it loads neither.
2.1.0
- Added KnowScapes (paid plan) as a provider of its own, for sites using a paid KnowScapes key rather than the free fair-use one. It is the same service and endpoint as the free entry, with a separate key slot, so both keys can be stored side by side. The free-key request form now appears only while the free entry is selected.
- Raised the default reply length from 1024 to 4096 tokens, and the accepted maximum from 8192 to 32768. 1024 tokens is roughly 750 words, which a thorough answer passes easily — the reply then stopped mid-sentence with nothing to say why. Installations still on the old default are raised to the new one on update; a value you set yourself is left untouched.
- A reply that runs into the length limit is now recognised as such. Visitors get a short neutral line saying the reply was cut short and that they can ask for the rest, and the settings screen names the limit that was hit and suggests a higher one. Previously a truncated reply was indistinguishable from a finished one.
- The reply length field now explains what the setting does and what it costs.
2.0.1
- Fixed the Save button doing nothing at all on the settings screen. The endpoint field was validated by the browser while being hidden for every provider except the custom one, and a browser refuses to submit a form holding an invalid control it cannot focus — with no message anywhere. The field is now validated on the server, which can report the problem properly.
- The endpoint URL is no longer discarded when a save does not include that field.
- Removed the 64-token step on the reply length, which rejected sensible values such as 1000.
- Turned off browser autofill on the endpoint and model fields.
2.0.0
- Added support for OpenAI, Anthropic, OpenRouter and any OpenAI-compatible endpoint alongside KnowScapes, each with its own stored key.
- Added a request form for a free KnowScapes fair-use key, with the key delivered by e-mail and an install link that opens a confirmation screen on this site.
- Removed the shared API key and agent identifier that earlier versions shipped as defaults. Every installation now supplies its own key.
- The widget now stays hidden until the site owner has consented to the outbound data transfer and switched it on explicitly.
- Replaced the direct cURL streaming transport with the WordPress HTTP API; replies are revealed progressively in the browser instead.
- Added a configurable system prompt and reply length cap.
- Added suggested privacy policy text via the WordPress privacy tools.
- Renamed all CSS classes, script handles and JavaScript globals to a plugin-specific prefix.
- Every string is now translatable and every output escaped.
- Uninstalling now removes the plugin's transients as well as its option, across every site of a multisite network.
- Added a site-wide daily ceiling next to the per-visitor rate limit, so the assistant cannot run up an unbounded bill under abuse.
- A provider reporting that an allowance is used up is now told apart from one reporting momentary overload. The provider's own wording and any link it supplies are shown to the site owner on the settings screen and clear themselves once the allowance is back; visitors see only a short neutral notice.
- The chat widget now recovers by itself from a stale nonce on a heavily cached page instead of asking the visitor to reload.
- Visitor messages are no longer passed through a tag stripper, which used to truncate any message containing a "<" character.
- Bundled marked and DOMPurify now ship unminified and readable, so no minified code remains anywhere in the package.
1.0.1
- Fixed the launcher being distorted by themes that set global button dimensions.
- Fixed the launcher icon not rendering in Firefox.
1.0.0
- Initial release.
Upgrade notices
2.3.0
Adds an AI notice under every chat, as required by the EU AI Act. It appears automatically after the update; reword it under Appearance → AI notice if the default does not suit your site.
2.2.1
Corrects the suggested privacy policy text, which implied something about your provider's retention that the plugin cannot know. If you copied that text into your privacy policy, re-copy it.
2.2.0
Adds the [knowscapes_ai_chat] shortcode, placement and access control, starter questions and optional page context. Nothing existing changes: the launcher keeps appearing everywhere until you say otherwise, and page context starts off.
2.1.0
Adds a provider entry for a paid KnowScapes key, and fixes replies stopping mid-sentence by raising the reply length limit and reporting when it is reached.
2.0.1
Fixes a Save button that silently did nothing on the settings screen. Update if your settings would not persist.
2.0.0
This release removes the shared API key that earlier versions shipped with. After updating, open the settings screen, supply your own key or request a free one, confirm the data transfer and switch the widget back on.