Authentication
API keys (for programs)
Pass your key as a bearer token on every AI/service request:
Authorization: Bearer kb_YOUR_KEY
On every /api/v1 route you can send the same key as x-api-key: kb_YOUR_KEY instead — the
header Anthropic SDKs use. If a request carries both headers they must hold the same key; different keys are refused
with 400 bad_request.
Keys are managed from Dashboard → API Keys. Each key can have:
- Expiry — a date, or "never". Expired keys return
401 key_expired. - Spending limits — up to one per period: per day, per week, per month and a total. Once
any of them is reached, requests return
402 key_budget_exhaustedeven while your plan quota is fine. - Models and Services — allow-lists that narrow the key to less than your account can do.
- Account management — whether the key may act on your account itself.
- Disabled — flip off without deleting.
You can hold up to 50 keys.
Spending limits
A key can carry several spending limits at once — at most one per period. Each is a dollar amount, separate from your plan quota. A key with no limits is unlimited (your plan quota and credit still apply).
| Period | Counts spend from… | Back to $0… |
|---|---|---|
day | 00:00 UTC today | at the next midnight UTC |
week | Monday 00:00 UTC | next Monday at 00:00 UTC |
month | the 1st of the month, 00:00 UTC | on the 1st of next month at 00:00 UTC |
total | when the limit was added | never on its own — only when you reset it |
The windows are calendar windows in UTC, not rolling ones. Every dollar the key spends counts toward all of its limits at once, so a key with $5 per day and $50 per month stops at whichever it reaches first.
- Blocked until reset. When any limit is reached the key returns
402 key_budget_exhausted, and keeps doing so until that window resets (or, fortotal, until you reset the counter). Your other keys and your plan quota are not affected. - Lower than what's spent is allowed. You can set a limit at or below what the key has already spent in the current window. The key is then blocked straight away, until the window resets.
- A new limit starts at $0. Adding a period counts spend from that moment on. Changing the amount of an existing limit keeps what it has counted so far.
- Reset a counter. The Reset button next to a limit — or
POST /api/keys/{id}/limits/{period}/reset— sets that window's counter back to $0 and unblocks the key. Usage history and stats are kept.
The 402 body names the limit that blocked the call:
{
"error": {
"code": "key_budget_exhausted",
"message": "API key credit limit exhausted",
"details": { "period": "day", "limit_usd": 5, "spent_usd": 5.02, "resets_at": "2026-10-28T00:00:00.000Z" }
}
}
resets_at is null for a total limit.
Setting limits through the API
Create or update a key with a limits list — one entry per period, each
limit_usd greater than zero. A PATCH replaces the whole list; send
[] (or null) to remove every limit.
curl -X PATCH https://khabot.com/api/keys/k_123 \
-H "Authorization: Bearer kb_ACCOUNT_KEY" -H "Content-Type: application/json" \
-d '{ "limits": [ { "period": "day", "limit_usd": 5 }, { "period": "month", "limit_usd": 50 } ] }'
Every key object — in the key list, the create response (which also carries the full key,
shown only that once), an update or a reset — reports its limits sorted day, week, month, total:
"limits": [
{ "period": "day", "limit_usd": 5, "spent_usd": 1.25, "remaining_usd": 3.75,
"resets_at": "2026-10-28T00:00:00.000Z", "blocked": false },
{ "period": "month", "limit_usd": 50, "spent_usd": 18.4, "remaining_usd": 31.6,
"resets_at": "2026-11-01T00:00:00.000Z", "blocked": false }
]
Managing keys is an account action, so these calls need a signed-in session or a key with Account management.
Scoping a key: models & services
By default a key can reach every model and service your plans cover. Narrow it when you hand a key to an app, a teammate, or CI:
- Models — pick a set, and anything else returns
403 model_not_allowed_for_key. - Services — pick a set, and calling any other service returns
403 service_not_allowed_for_key. Chat / AI completions is always available.
Scoping only ever subtracts: a key can never do more than your plans already allow, so a
model outside your plans still returns 403 model_not_in_plan.
When the Documents service is in scope the key also takes a document-key filter, which limits which stored documents it may touch.
When Object storage is in scope the key also takes
storage scopes: all buckets, or a list of specific buckets with full or
read-only access each. A key limited to specific buckets can't create or delete buckets, and anything outside
its scopes returns 403 storage_scope_denied.
A key's own usage
Any key can read its own usage — no Account management needed. Both endpoints only ever return the calling key's
requests; a dashboard session (no API key) gets 400 bad_request.
| Endpoint | Returns |
|---|---|
GET /api/v1/usage/events?limit=&before= | The key's requests, newest first: {"events": [...], "next_before": 1234}. limit is 1–200 (default 50); pass next_before back as before for the next page |
GET /api/v1/usage/series?days= | Billed spend per UTC day: {"series": [{"date": "2026-10-29", "billed_usd": 0.42}, …]} — every day present, days 1–90 (default 14) |
curl "https://khabot.com/api/v1/usage/events?limit=2" -H "Authorization: Bearer kb_YOUR_KEY"
{
"events": [
{ "id": 5121, "ts": "2026-10-29T09:12:44.000Z", "request_id": "kb_req_9f2c", "khabot_model": "khabot/glm-5.2",
"service_tag": null, "input_tokens": 1830, "output_tokens": 212, "cost_billed_usd": 0.004101, "error_code": null }
],
"next_before": 5121
}
Trimmed — events carry a few more fields. Reading usage doesn't count against your rate limit.
Account management
Service calls (chat, images, speech, search, documents…) work with any key. Endpoints that manage the
account — plans, billing, keys themselves, and admin actions when your account is an admin —
require a key with Account management switched on; without it they return
403 key_not_authorized.
Leave it off for keys you ship inside an app. A key that carries it can do anything you can do in the dashboard, so treat it like your password.
What can this key do? GET /api/v1/key-info
Any key can introspect itself — no Account management needed. Useful when an app, a device or a SaaS integration is handed a key and wants to fail early or show how much room is left:
curl https://khabot.com/api/v1/key-info \
-H "Authorization: Bearer kb_YOUR_KEY"
{
"key": {
"id": "k_123", "name": "kiosk", "last4": "c3d4",
"created_at": "2026-10-01T09:00:00.000Z", "last_used_at": "2026-10-27T08:12:00.000Z",
"expires_at": null, "disabled": false,
"limits": [
{ "period": "day", "limit_usd": 5, "spent_usd": 1.25, "remaining_usd": 3.75,
"resets_at": "2026-10-28T00:00:00.000Z", "blocked": false }
],
"models": null, "services": ["tts"], "documents_key_filter": null,
"storage_scopes": null, "account_management": false
},
"effective_remaining_usd": 3.75,
"credit_usd": 12.4,
"quotas": [ … ],
"rate_limit": { "rpm": 60, "burst": 20, "remaining": 17, "used": 3, "resets_at": "2026-10-27T10:15:03.000Z" }
}
| Field | Meaning |
|---|---|
key | The key itself, in the same shape as the key list: its limits, allow-lists
(null = unrestricted), document filter, storage scopes and whether account management is on |
effective_remaining_usd | The room left under the key's tightest limit — the
smallest remaining_usd — or null when the key has no limits |
credit_usd | Your account's credit balance |
quotas | Your active plans' quota windows, as shown on the Usage page |
rate_limit | Your account's request bucket — sustained rpm, burst, what's remaining now, used, and resets_at (when at least one more request is allowed). See Rate limits |
For a single progress bar use effective_remaining_usd; for one bar per limit use each
limit's spent_usd against its limit_usd, with resets_at as the
"resets in…" label:
const info = await fetch("https://khabot.com/api/v1/key-info", {
headers: { Authorization: "Bearer " + KEY },
}).then((r) => r.json());
for (const l of info.key.limits) {
const pct = Math.min(100, (l.spent_usd / l.limit_usd) * 100);
drawBar(l.period, pct, l.blocked ? "blocked" : `$${l.remaining_usd.toFixed(2)} left`);
}
A dashboard session (no API key) gets 400 bad_request — key-info is about the key making
the call.
Usage stats for a key
GET /api/keys/{id}/stats — requests, spend and errors for one key over time. The same data
is behind the Stats button on Dashboard → API Keys.
Like the other key endpoints it needs a session or a key with Account management.
| Parameter | Values | Default |
|---|---|---|
range | 7d, 30d, 90d (the last N calendar days in UTC, today included),
month (since the 1st) or all (since the key was created) | 30d |
granularity | day or month — how the series is grouped | day |
curl "https://khabot.com/api/keys/k_123/stats?range=7d&granularity=day" \
-H "Authorization: Bearer kb_ACCOUNT_KEY"
{
"key_id": "k_123",
"range": { "from": "2026-10-21T00:00:00.000Z", "to": "2026-10-27T10:30:00.000Z" },
"totals": { "requests": 412, "billed_usd": 3.18, "errors": 2 },
"series": [
{ "period": "2026-10-21", "requests": 51, "billed_usd": 0.42 },
{ "period": "2026-10-23", "requests": 97, "billed_usd": 0.71 }
],
"by_service": [ { "service_family": "chat", "requests": 380, "billed_usd": 2.9 } ],
"by_model": [ { "model": "khabot/glm-5.2", "requests": 380, "billed_usd": 2.9 } ]
}
serieslists only the days (YYYY-MM-DD) or months (YYYY-MM) that had usage, oldest first.by_servicegroups chat and pass-through calls underchat; other rows use the service ids fromGET /api/services.by_modellists up to 20 models by spend; service calls appear asservice:<name>.billed_usdis what the key was charged — the same amounts its spending limits count. Resetting a limit's counter doesn't change these numbers.