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 & path | Returns |
|---|---|
GET /api/v1/kbh/releases/latest | The newest release for a platform and channel |
GET /api/v1/kbh/releases | Recent 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/token | Sign-in with a code |
GET /api/v1/kbh/extensions/index.json | The extension index |
POST /api/v1/kbh/feedback | Feedback, bug and crash reports |
Platforms, channels and kinds
| Parameter | Values |
|---|---|
platform | linux-x64, win-x64, darwin-arm64 — required |
channel | stable (default) or beta. The beta channel offers the newest of beta and stable, so beta users never fall behind a stable release. |
kind | cli (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"
}
| Field | Meaning |
|---|---|
kind | cli or desktop |
version | The release's version (strict semver) |
filename | The downloaded file's name: kbh-<platform>-<version> (.exe on Windows) for cli; the installer's own name — .deb, .AppImage, .exe, .msi… — for desktop |
url | Absolute HTTPS download link. Always use it as given — the download host can change. |
sha256, size | The binary's SHA-256 (hex) and size in bytes — check both after downloading |
signature | The release signature, or null — see Signatures |
notes_md | Release notes in Markdown |
min_version | Clients older than this must update before they run, or null |
published_at | When 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 answers206withContent-Range; a range past the end answers416; several ranges at once get the whole file with200. - Caching — responses carry an
ETag; send it back inIf-None-Matchfor a304, or inIf-Rangeto resume only if the file is unchanged.HEADworks 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.
- Start.
POST /api/v1/auth/devicewith{"client": "kbh", "client_version": "1.4.0", "hostname": "devbox"}(no key needed):
Keep{ "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 }device_codesecret — it is what collects the key. Show the user theuser_codeand openverification_uri_completein their browser. - 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.
- Poll. Meanwhile, every
intervalseconds,POST /api/v1/auth/device/tokenwith{"device_code": "…"}:
| Answer | Meaning |
|---|---|
428 authorization_pending | Not decided yet — keep polling |
429 slow_down | You polled too fast. The response carries a new, longer interval — use it from now on |
403 access_denied | The user denied the sign-in — stop |
410 expired_token | The 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"
}
]
}
kindis one ofagent,skill,tool,provider,pack.latestis the newest published version that hasn't been withdrawn; an extension with nothing published isn't listed.min_kbh_versionis the oldest app version it runs on (ornull).signatureandhomepageappear 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…" } ]
}'
| Field | Notes |
|---|---|
kind | feedback, bug or crash — required |
text | Required, up to 20,000 characters |
version, platform, os_release | The app version, its platform and the operating system |
context | Optional: session_id, model and provider strings from the app; anything else is dropped |
attachments | Optional: 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.