KhaBot app

The KhaBot app uses khabot.com for its updates, its sign-in, its extensions and its feedback. The release and extension endpoints need no API key and are cacheable, so install scripts and package mirrors can use them too.

Install

One line installs the latest stable build — it fetches the manifest below, downloads the build, checks its SHA-256 and runs the build's own installer:

# Linux (x64)
curl -fsSL https://khabot.com/install.sh | sh

# Windows (PowerShell)
irm https://khabot.com/install.ps1 | iex

Every build, with its size, checksum and release notes, is on the download page.

Endpoints

Method & pathReturns
GET /api/v1/kbh/releases/latestThe newest release for a platform and channel
GET /api/v1/kbh/releasesRecent releases, newest first
GET /api/v1/kbh/dl/{platform}/{version}/{file}The app build itself
GET /api/v1/kbh/dl/desktop/{platform}/{version}/{filename}A desktop installer
POST /api/v1/auth/device, POST /api/v1/auth/device/tokenSign-in with a code
GET /api/v1/kbh/extensions/index.jsonThe extension index
POST /api/v1/kbh/feedbackFeedback, bug and crash reports

Platforms, channels and kinds

ParameterValues
platformlinux-x64, win-x64, darwin-arm64 — required
channelstable (default) or beta. The beta channel offers the newest of beta and stable, so beta users never fall behind a stable release.
kindcli (default) — the app runtime, or desktop — the desktop installer packages. Leaving it out keeps the answer the runtime, as before.

Versions are strict semver — 1.4.0, or 1.5.0-beta.1 on the beta channel — with no leading v. Any parameter not listed here is refused with 400 bad_request.

Latest release

curl "https://khabot.com/api/v1/kbh/releases/latest?platform=linux-x64&channel=stable"
{
  "kind": "cli",
  "version": "1.4.0",
  "channel": "stable",
  "platform": "linux-x64",
  "filename": "kbh-linux-x64-1.4.0",
  "url": "https://khabot.com/api/v1/kbh/dl/linux-x64/1.4.0/kbh-linux-x64-1.4.0",
  "sha256": "9f2c…e41a",
  "size": 48213504,
  "signature": "kfP4…zQ==",
  "notes_md": "- Faster startup\n- New model picker",
  "min_version": "1.2.0",
  "published_at": "2026-10-28T14:02:11.000Z"
}
FieldMeaning
kindcli or desktop
versionThe release's version (strict semver)
filenameThe downloaded file's name: kbh-<platform>-<version> (.exe on Windows) for cli; the installer's own name — .deb, .AppImage, .exe, .msi… — for desktop
urlAbsolute HTTPS download link. Always use it as given — the download host can change.
sha256, sizeThe binary's SHA-256 (hex) and size in bytes — check both after downloading
signatureThe release signature, or null — see Signatures
notes_mdRelease notes in Markdown
min_versionClients older than this must update before they run, or null
published_atWhen the release went live

A release that has been withdrawn (yanked) is never returned as the latest. When nothing matches — for example kind=desktop before the first desktop release — the answer is 404 not_found.

Signatures

signature is the base64 of an ed25519 signature over the 32 raw bytes of the archive's sha256 — not over the hex string, and not over the file. khabot.com stores it verbatim (1–128 characters, base64 alphabet) and does not verify it; the client verifies it against the public keys baked into its own build.

Release list

curl "https://khabot.com/api/v1/kbh/releases?platform=win-x64&channel=beta&limit=5"

Returns { "releases": [ … ] } — the same objects as latest, newest first. limit caps how many come back.

Downloading

GET the release's url. The download endpoint is built for resuming and caching:

  • Resume — a single Range: bytes=… request answers 206 with Content-Range; a range past the end answers 416; several ranges at once get the whole file with 200.
  • Caching — responses carry an ETag; send it back in If-None-Match for a 304, or in If-Range to resume only if the file is unchanged. HEAD works too.
  • Free — downloads are never billed and don't count against your API rate limit.
  • Fair use — each IP address may run up to 3 downloads at once and fetch up to 2 GB per hour; past that the server answers 429 — wait and retry.
# download (resumable) and check it
URL=$(curl -s "https://khabot.com/api/v1/kbh/releases/latest?platform=linux-x64" | jq -r .url)
curl -fL -C - -o kbh "$URL"
sha256sum kbh        # must equal the manifest's sha256

App builds are named kbh-<platform>-<version>, with .exe on Windows — for example kbh-linux-x64-1.4.0 or kbh-win-x64-1.4.0.exe. Desktop installers keep their own names and live under /desktop/: https://khabot.com/api/v1/kbh/dl/desktop/linux-x64/1.4.0/KhaBot-1.4.0-x86_64.AppImage.

Sign-in with a code

Instead of pasting an API key, the app can sign in through the browser. It asks for a short code, the user approves it at khabot.com/device, and the app receives its own API key.

  1. Start. POST /api/v1/auth/device with {"client": "kbh", "client_version": "1.4.0", "hostname": "devbox"} (no key needed):
    {
      "device_code": "Zx8…long secret…",
      "user_code": "BCDFG-HJKLM",
      "verification_uri": "https://khabot.com/device",
      "verification_uri_complete": "https://khabot.com/device?code=BCDFG-HJKLM",
      "expires_in": 600,
      "interval": 5
    }
    Keep device_code secret — it is what collects the key. Show the user the user_code and open verification_uri_complete in their browser.
  2. Approve. On that page the user — signed in to their account — sees the app, its version, the computer's name and the address the request came from. They can name the key and give it spending limits, then confirm with their password.
  3. Poll. Meanwhile, every interval seconds, POST /api/v1/auth/device/token with {"device_code": "…"}:
AnswerMeaning
428 authorization_pendingNot decided yet — keep polling
429 slow_downYou polled too fast. The response carries a new, longer interval — use it from now on
403 access_deniedThe user denied the sign-in — stop
410 expired_tokenThe code expired (after 10 minutes), was already used, or isn't known — start over
200{"key": "kb_…", "key_id": "k_…", "account_email": "…", "base_url": "https://khabot.com/api/v1"} — delivered once; the next poll gets 410

The key is never shown in the browser; it only travels to the app. It is named kbh <hostname> — with  · <last 4 characters> added if the account already has a key by that name — and the user can revoke it any time under API keys. Starting a sign-in is limited per address (10 an hour).

Extensions

The app's extensions — agents, skills, tools, providers and packs — are listed in one index:

curl https://khabot.com/api/v1/kbh/extensions/index.json
{
  "version": 1,
  "generated_at": "2026-10-29T09:00:00.000Z",
  "extensions": [
    {
      "id": "web-tools",
      "name": "Web tools",
      "kind": "tool",
      "description": "Fetch pages and search the web from an agent.",
      "latest": {
        "version": "1.0.0",
        "url": "https://khabot.com/api/v1/kbh/dl/ext/web-tools/1.0.0/web-tools-1.0.0.tar.gz",
        "sha256": "3b7e…90af",
        "size": 1840,
        "min_kbh_version": "1.0.0",
        "signature": "…"
      },
      "homepage": "https://…",
      "publisher": "KhaBot"
    }
  ]
}
  • kind is one of agent, skill, tool, provider, pack.
  • latest is the newest published version that hasn't been withdrawn; an extension with nothing published isn't listed. min_kbh_version is the oldest app version it runs on (or null).
  • signature and homepage appear only when set. Like release signatures, extension signatures are stored and published as-is — khabot.com does not verify them.
  • The index takes no query parameters (any is a 400) and may be cached for up to 5 minutes.

Archives are .tar.gz files at GET /api/v1/kbh/dl/ext/{id}/{version}/{id}-{version}.tar.gz — use the url as given. Check sha256 and size after downloading. Resume, caching and the per-address limits work as for app downloads.

Feedback and crash reports

POST /api/v1/kbh/feedback sends feedback, a bug report or a crash report to the KhaBot team. It is never billed and doesn't touch your rate limit.

curl https://khabot.com/api/v1/kbh/feedback \
  -H "Authorization: Bearer kb_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{
    "kind": "bug",
    "version": "1.4.0",
    "platform": "linux-x64",
    "os_release": "Ubuntu 24.04",
    "text": "Tool output with emoji is cut off in the transcript.",
    "context": { "session_id": "s_42", "model": "khabot/glm-5.2" },
    "attachments": [ { "name": "transcript.txt", "content_type": "text/plain", "data_base64": "SGVsbG8…" } ]
  }'
FieldNotes
kindfeedback, bug or crash — required
textRequired, up to 20,000 characters
version, platform, os_releaseThe app version, its platform and the operating system
contextOptional: session_id, model and provider strings from the app; anything else is dropped
attachmentsOptional: up to 5 files, 5 MB in total once decoded. name is a plain file name (letters, digits, ., _, -)

It answers 201 {"id": "fb_…"}. With a key, you can send 10 reports an hour per key; past that the answer is 429 with retry-after.

Without a key only crash reports are accepted — so an app that crashes before it has signed in can still report it. An anonymous crash report may carry one attachment of up to 1 MB, must name a version that has been published, and each address may send 3 an hour. Anything else without a key gets 401.