Documentação

A documentação para programadores está em inglês.

Documentação

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
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"
    }
  }'
server.mjs
// 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.

FieldMeaning
matchedtrue when WebKnox answered. false: reply with your own model; the turn is not billed.
textThe answer as plain text. Enough on its own for a text-only chat, and the accessible alternative to the card.
speechA shorter text, written to be read aloud.
card{type, version, data}, or null. data follows the JSON Schema for that card type and version.
sourcesWhere the data came from, with a license where the source requires one. Show them.
follow_upsUp to three questions people tend to ask next. Send one as the next user message.
usage.billableWhether this turn counted toward your tokens.
attributionOn 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.

Terminal
npm install @webknox/elements
index.html
<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.

Unmatched response
{
  "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.

HTTPcodeWhen
400invalid_requestNo messages, the last turn is not from the user, an unknown locale…
401unauthorizedThe key is missing or revoked.
403plan_requiredThe request asks for something its plan doesn't include: hosted render URLs (render) need Pro or Scale.
429rate_limitedToo many requests per second for your plan. Wait retry_after_s.
429quota_exceededNo 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, 503internal, unavailableOur 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