# Knowa Website API — Developer & AI-Agent Documentation

> Version 1 · Last updated 2026-08-11 · This document is self-contained: everything needed to integrate the Knowa support bot into any website (or any client that can send HTTPS requests) is on this page. It is intentionally written so both human developers and AI coding agents can implement an integration without further context.

Knowa is a white-label AI support bot. Each customer ("tenant") has their own Telegram bot, brand, and knowledge base — all managed by the customer's admin from inside Telegram. The Website API exposes that **same bot** over REST so it can also answer on your website: same knowledge base, same tone/language settings, same human-handoff behaviour.

## Quick facts (TL;DR for AI agents)

| Item | Value |
| --- | --- |
| Base URL | `https://api.knowa.solutions` (self-hosted deployments: the bot server's `PUBLIC_URL`) |
| Protocol | HTTPS, JSON request/response bodies (`Content-Type: application/json`) |
| Auth | `Authorization: Bearer knw_live_...` header (alternative: `X-Api-Key: knw_live_...`) |
| Endpoints | `POST /api/v1/ask`, `GET /api/v1/status` |
| CORS | Enabled for all origins; keys can be restricted to specific origins server-side |
| Rate limit | 60 requests/minute per API key (HTTP 429 + `Retry-After` when exceeded) |
| Success envelope | `{ "ok": true, ... }` |
| Error envelope | `{ "ok": false, "error": { "code": string, "message": string } }` |
| Unknown questions | `answer` is `null`, `escalated` is `true`, and the tenant's admin is notified on Telegram automatically |
| Zero-code option | `<script src="https://api.knowa.solutions/widget.js" data-api-key="knw_live_..."></script>` |

## 1. Getting an API key

API keys are issued **by the Knowa super-admin**, one or more per tenant bot:

1. You need an active Knowa tenant bot first (created via Telegram — the normal Knowa onboarding: create a bot with @BotFather, contact sales, and your admin manages docs and settings from inside your own bot).
2. Ask for API access ([@KnowaAiBot](https://t.me/KnowaAiBot) or your sales contact). The super-admin creates a key in the master bot with a label and an optional **origin allow-list** (the domains your website runs on).
3. You receive the full key **once** (format `knw_live_` + 48 hex chars). Only a SHA-256 hash is stored server-side, so save it somewhere safe. Keys can be revoked at any time.

The tenant admin keeps full control in Telegram: they manage the knowledge base and settings from their bot's admin panel (`/start` → 🔌 Website API shows active keys), and they receive every escalated question and integration error as a Telegram message.

### Key security notes

- For a public website widget the key is necessarily visible in the browser. This is expected — protect it with the **origin allow-list** (requests from other websites' browsers are rejected with `403 origin_not_allowed`) and the per-key rate limit.
- For server-to-server use, keep the key in an environment variable and never commit it.
- Compromised key? Ask the super-admin to revoke it and issue a new one; revocation is immediate.

## 2. Authentication

Send the key on every request, either way:

```
Authorization: Bearer knw_live_abc123...
```

or

```
X-Api-Key: knw_live_abc123...
```

Missing key → `401 missing_api_key`. Unknown/revoked key → `401 invalid_api_key`. Tenant subscription paused/expired → `403 subscription_inactive`.

## 3. Endpoints

### 3.1 `POST /api/v1/ask` — ask the bot a question

Request body (JSON):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `question` | string, 1–4000 chars | yes | The user's question, plain text. Any language — the bot replies in the user's language (or the tenant's configured language). |
| `session_id` | string, `[A-Za-z0-9_-]{1,64}` | no | Client-generated conversation id. When provided, the API keeps the last 3 Q/A turns for 15 minutes so follow-up questions ("what about pricing?") have context. Use one id per visitor conversation. |
| `visitor_name` | string, ≤100 chars | no | Shown to the human admin if the question gets escalated. |
| `page_url` | string, ≤500 chars | no | Page the question was asked from; included in escalations so the admin has context. |

Successful response — answered (HTTP 200):

```json
{
  "ok": true,
  "request_id": "f3a09c1d2b4e5a67",
  "answer": "You can reset your password from Settings → Security → Reset password. A reset link is valid for 30 minutes.",
  "escalated": false,
  "cached": false
}
```

Successful response — bot doesn't know (HTTP 200):

```json
{
  "ok": true,
  "request_id": "9d2c4b1a0e8f7c65",
  "answer": null,
  "escalated": true,
  "cached": false,
  "fallback_message": "I don't have that answer yet, but I've forwarded your question to the team — they'll follow up shortly."
}
```

Semantics you must implement:

- If `answer` is a string → display it to the user (it is plain text; preserve newlines).
- If `answer` is `null` and `escalated` is `true` → the bot found nothing reliable in the knowledge base. It **never invents answers**. The exact question (plus `visitor_name` and `page_url` if given) has already been sent to the tenant's human admin on Telegram. Show `fallback_message` (or your own copy) to the user.
- `cached` is `true` when the answer came from the per-tenant answer cache (identical recent question).
- `request_id` is a server-generated id for support/debugging; log it.

### 3.2 `GET /api/v1/status` — bot status and usage

No request body. Response (HTTP 200):

```json
{
  "ok": true,
  "bot": {
    "brand_name": "AcmeCorp",
    "status": "active",
    "expires_at": "2027-02-11T00:00:00.000Z"
  },
  "knowledge_base": { "documents": 12, "indexed_chunks": 431 },
  "usage": { "questions_answered": 1289 }
}
```

Use it for health checks and to verify a key works before going live.

## 4. Errors

All errors use the envelope `{ "ok": false, "error": { "code", "message", ... } }`.

| HTTP | `error.code` | Meaning | What to do |
| --- | --- | --- | --- |
| 400 | `invalid_request` | Body failed validation; `error.details` lists field problems | Fix the request |
| 401 | `missing_api_key` | No `Authorization` / `X-Api-Key` header | Send the key |
| 401 | `invalid_api_key` | Key unknown or revoked | Check the key; request a new one if revoked |
| 403 | `origin_not_allowed` | Browser `Origin` not on the key's allow-list | Ask the super-admin to add your domain |
| 403 | `subscription_inactive` | Tenant bot is paused or expired | Tenant admin must renew |
| 429 | `rate_limited` | Over the per-key limit; `retry_after_seconds` + `Retry-After` header included | Back off and retry |
| 500 | `internal_error` | Server-side failure; includes `request_id` | Retry with backoff. The tenant admin is automatically notified on Telegram (deduplicated), so persistent failures are already being looked at |

Recommended client behaviour: treat any non-2xx as "show a friendly error and let the user retry"; never retry more than a few times; honour `Retry-After`.

## 5. Rate limits

60 requests per minute per API key (fixed one-minute window; deployments can change the default). The widget and typical chat UIs stay far below this. If you expect bursts above it, ask for a second key or a raised limit.

## 6. Conversation context (sessions)

The API is stateless unless you pass `session_id`. With it:

- The last **3** question/answer turns are kept for **15 minutes** (rolling).
- Follow-ups are answered with that context, mirroring how the Telegram bot handles DM follow-ups.
- Generate one id per visitor conversation, e.g. `crypto.randomUUID()` stored in `localStorage`. Don't reuse ids across visitors — context would leak between them.

## 7. Zero-code integration: the chat widget

One script tag renders a floating chat bubble (bottom corner), a chat panel, typing indicator, and session handling — no build step, no dependencies, ~6 KB:

```html
<script
  src="https://api.knowa.solutions/widget.js"
  data-api-key="knw_live_YOUR_KEY"
  data-brand="Acme Support"
  data-color="#2563eb"
  data-position="right"
  data-greeting="Hi! Ask me anything about Acme."
></script>
```

Attributes: `data-api-key` (required); `data-brand` (header title, default "Support"); `data-color` (accent, default `#2563eb`); `data-position` (`right`/`left`, default `right`); `data-greeting` (first bot message); `data-base` (override API base URL — only needed if the script is served from a different host than the API).

The widget already implements everything in this document: auth, sessions, escalation fallbacks, and error states.

## 8. Code examples

### curl

```bash
curl -X POST https://api.knowa.solutions/api/v1/ask \
  -H "Authorization: Bearer knw_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "How do I reset my password?",
    "session_id": "visitor-42-session-1",
    "visitor_name": "Jane",
    "page_url": "https://acme.com/help"
  }'
```

### JavaScript (browser or Node 18+)

```js
async function askSupport(question, sessionId) {
  const res = await fetch("https://api.knowa.solutions/api/v1/ask", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer knw_live_YOUR_KEY",
    },
    body: JSON.stringify({
      question,
      session_id: sessionId,
      page_url: typeof location !== "undefined" ? location.href : undefined,
    }),
  });
  const data = await res.json();
  if (!data.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
  // data.answer is null when escalated — show the fallback instead.
  return data.answer ?? data.fallback_message;
}
```

### Python

```python
import requests

API_KEY = "knw_live_YOUR_KEY"
BASE = "https://api.knowa.solutions"

def ask_support(question: str, session_id: str | None = None) -> str:
    r = requests.post(
        f"{BASE}/api/v1/ask",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={"question": question, "session_id": session_id},
        timeout=30,
    )
    data = r.json()
    if not data.get("ok"):
        raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
    return data["answer"] or data["fallback_message"]
```

### React hook (minimal)

```jsx
import { useCallback, useRef, useState } from "react";

export function useKnowa(apiKey, base = "https://api.knowa.solutions") {
  const [loading, setLoading] = useState(false);
  const session = useRef(crypto.randomUUID());

  const ask = useCallback(
    async (question) => {
      setLoading(true);
      try {
        const res = await fetch(`${base}/api/v1/ask`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${apiKey}`,
          },
          body: JSON.stringify({ question, session_id: session.current }),
        });
        const data = await res.json();
        if (!data.ok) throw new Error(data.error.message);
        return { text: data.answer ?? data.fallback_message, escalated: data.escalated };
      } finally {
        setLoading(false);
      }
    },
    [apiKey, base]
  );

  return { ask, loading };
}
```

## 9. Integration checklist (for AI agents)

1. Obtain the API key and (optionally) confirm the allowed origins cover the target site.
2. Call `GET /api/v1/status` with the key; expect `ok: true` and `bot.status == "active"`.
3. Either embed `widget.js` (fastest, recommended) **or** build a custom UI that:
   - POSTs to `/api/v1/ask` with `question` + a per-visitor `session_id`;
   - renders `answer` as plain text preserving newlines;
   - renders `fallback_message` when `answer` is `null`;
   - shows a retry-friendly error state on non-2xx and honours `Retry-After` on 429.
4. Pass `visitor_name` / `page_url` when available — they make human escalations far more useful.
5. Nothing else is required: escalation to the human admin and error alerting happen server-side automatically.

## 10. OpenAPI 3.1 specification

```yaml
openapi: 3.1.0
info:
  title: Knowa Website API
  version: "1.0"
  description: REST access to a Knowa tenant support bot (RAG over the tenant's docs).
servers:
  - url: https://api.knowa.solutions
components:
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      description: "API key, format: knw_live_<48 hex chars>"
  schemas:
    Error:
      type: object
      required: [ok, error]
      properties:
        ok: { const: false }
        error:
          type: object
          required: [code, message]
          properties:
            code: { type: string }
            message: { type: string }
security:
  - bearer: []
paths:
  /api/v1/ask:
    post:
      operationId: askQuestion
      summary: Ask the support bot a question
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [question]
              properties:
                question: { type: string, minLength: 1, maxLength: 4000 }
                session_id: { type: string, pattern: "^[A-Za-z0-9_-]{1,64}$" }
                visitor_name: { type: string, maxLength: 100 }
                page_url: { type: string, maxLength: 500 }
      responses:
        "200":
          description: Answered, or escalated to a human (answer is null).
          content:
            application/json:
              schema:
                type: object
                required: [ok, request_id, answer, escalated, cached]
                properties:
                  ok: { const: true }
                  request_id: { type: string }
                  answer: { type: [string, "null"] }
                  escalated: { type: boolean }
                  cached: { type: boolean }
                  fallback_message: { type: string }
        "400": { $ref: "#/components/responses/Err" }
        "401": { $ref: "#/components/responses/Err" }
        "403": { $ref: "#/components/responses/Err" }
        "429": { $ref: "#/components/responses/Err" }
        "500": { $ref: "#/components/responses/Err" }
  /api/v1/status:
    get:
      operationId: getStatus
      summary: Bot status, knowledge-base size, and usage
      responses:
        "200":
          description: Status payload.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { const: true }
                  bot:
                    type: object
                    properties:
                      brand_name: { type: string }
                      status: { type: string, enum: [active, paused, expired] }
                      expires_at: { type: [string, "null"], format: date-time }
                  knowledge_base:
                    type: object
                    properties:
                      documents: { type: integer }
                      indexed_chunks: { type: integer }
                  usage:
                    type: object
                    properties:
                      questions_answered: { type: integer }
```

*(Responses object `Err` refers to the shared `Error` schema with the matching HTTP status.)*

## 11. Support

- Telegram: [@KnowaAiBot](https://t.me/KnowaAiBot)
- Email: support@knowa.solutions
- Human-readable docs: https://knowa.solutions/developers
- This file (raw markdown, stable URL for AI agents): https://knowa.solutions/api.md
