Docs
Quickstart
Send the conversation you already have. When WebKnox can answer the last question with sourced data, you get a typed card plus plain text. When it can't, you get matched: false and reply with your own model, at no cost.
1. Get an API key
Create a key in the console. The free plan includes 50 tokens a day; a matched answer costs one.
Keep the key on your server, for example in WEBKNOX_API_KEY. Never put it into browser or app code.
2. Ask a question
Post the conversation to https://api.webknox.com/v2/answers. Pass your history as it is: earlier turns resolve follow-ups such as “and tomorrow?”. context is optional and tells WebKnox where and when the user is.
curl https://api.webknox.com/v2/answers \
-H "Authorization: Bearer $WEBKNOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "user",
"content": "What is the weather in Dresden?"
}
],
"locale": "en",
"context": {
"timezone": "Europe/Berlin",
"now": "2026-10-21T14:00:00+02:00",
"location": {
"lat": 51.05,
"lon": 13.74
},
"units": "metric"
}
}'
// Runs on your server, so the key never reaches the browser.
export async function askWebKnox(messages) {
const res = await fetch('https://api.webknox.com/v2/answers', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WEBKNOX_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messages,
locale: "en",
context: {
"timezone": "Europe/Berlin",
"now": "2026-10-21T14:00:00+02:00",
"location": {
"lat": 51.05,
"lon": 13.74
},
"units": "metric"
},
}),
});
if (!res.ok) throw new Error(`WebKnox answered ${res.status}`);
return res.json();
}
const answer = await askWebKnox([
{
"role": "user",
"content": "What is the weather in Dresden?"
}
]);
if (!answer.matched) {
// Not billed: reply with your own model instead.
} else {
// Render answer.card with <webknox-answer>, or show answer.text.
}
3. Read the response
Every answered or unmatched turn returns HTTP 200. New optional fields can appear at any time; ignore what you don't know.
| Field | Meaning |
|---|---|
matched | true when WebKnox answered. false: reply with your own model; the turn is not billed. |
text | The answer as plain text. Enough on its own for a text-only chat, and the accessible alternative to the card. |
speech | A shorter text, written to be read aloud. |
card | {type, version, data}, or null. data follows the JSON Schema for that card type and version. |
sources | Where the data came from, with a license where the source requires one. Show them. |
follow_ups | Up to three questions people tend to ask next. Send one as the next user message. |
usage.billable | Whether this turn counted toward your tokens. |
attribution | On the free plan: {text, url}, a small "Answer by WebKnox" credit. Show it with the answer; the element does. |
4. Show the answer
The response holds data, never HTML. <webknox-answer> renders any card in any framework, with built-in themes such as daylight, midnight, kiosk and kids.
npm install @webknox/elements
<webknox-answer theme="daylight"></webknox-answer>
<script type="module">
import '@webknox/elements';
const answer = await fetch('/api/ask', { // your server calls WebKnox
method: 'POST',
body: JSON.stringify({ messages }),
}).then((res) => res.json());
document.querySelector('webknox-answer').answer = answer;
</script>
The element fires webknox-follow-up when someone taps a follow-up; send event.detail.text as the next user message. Try every card, theme and layout in the Answer Lab.
On Pro and Scale you can ask for render instead: an iframe URL and an image URL of the card, hosted for 30 days (see Rendering).
5. When nothing matches
WebKnox doesn't answer what a general chat model already does well: explanations, smalltalk, writing help. Those turns come back unmatched and are not billed, so your own model replies.
{
"v": 2,
"id": "ans_d5f0638eeb02",
"matched": false,
"reason": "no_rich_answer",
"locale": "en",
"text": null,
"card": null,
"sources": [],
"follow_ups": [],
"actions": [],
"usage": {
"billable": false
}
}
6. Errors and limits
Other responses carry {"error": {"code", "message"}}; a 429 adds retry_after_s.
| HTTP | code | When |
|---|---|---|
| 400 | invalid_request | No messages, the last turn is not from the user, an unknown locale… |
| 401 | unauthorized | The key is missing or revoked. |
| 403 | plan_required | The request asks for something its plan doesn't include: hosted render URLs (render) need Pro or Scale. |
| 429 | rate_limited | Too many requests per second for your plan. Wait retry_after_s. |
| 429 | quota_exceeded | No tokens left today: the daily quota and any packs are used up. Wait retry_after_s (until 00:00 UTC) or buy a pack (packs need a paid plan). |
| 500, 503 | internal, unavailable | Our fault. Retry with backoff. |
Your plan's tokens reset at 00:00 UTC. With token packs or auto-recharge, requests keep going after the daily quota; see Pricing.
7. Next steps
- Pick a theme and layout in the Answer Lab and copy its code.
- See how others use WebKnox: chat apps, displays, voice assistants and support bots.
- Every field, error and card: the API reference and the card types.
- Questions? Write to us.