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_exhausted even 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).

PeriodCounts spend from…Back to $0…
day00:00 UTC todayat the next midnight UTC
weekMonday 00:00 UTCnext Monday at 00:00 UTC
monththe 1st of the month, 00:00 UTCon the 1st of next month at 00:00 UTC
totalwhen the limit was addednever 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, for total, 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.

EndpointReturns
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" }
}
FieldMeaning
keyThe 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_usdThe room left under the key's tightest limit — the smallest remaining_usd — or null when the key has no limits
credit_usdYour account's credit balance
quotasYour active plans' quota windows, as shown on the Usage page
rate_limitYour 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.

ParameterValuesDefault
range7d, 30d, 90d (the last N calendar days in UTC, today included), month (since the 1st) or all (since the key was created)30d
granularityday or month — how the series is groupedday
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 } ]
}
  • series lists only the days (YYYY-MM-DD) or months (YYYY-MM) that had usage, oldest first.
  • by_service groups chat and pass-through calls under chat; other rows use the service ids from GET /api/services.
  • by_model lists up to 20 models by spend; service calls appear as service:<name>.
  • billed_usd is what the key was charged — the same amounts its spending limits count. Resetting a limit's counter doesn't change these numbers.