> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kataven.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> How the Hub API authenticates requests and routes them to your account.

The Hub API accepts two credential types in `Authorization: Bearer <token>`:

| Token shape                                         | When to use                         | X-Account-ID required?            |
| --------------------------------------------------- | ----------------------------------- | --------------------------------- |
| **`sk_live_<account>_<rest>`** — long-lived API key | SDKs, MCP, CI, server-to-server     | No — the account is in the prefix |
| **Zitadel JWT** (`eyJ…`)                            | Hub UI / interactive admin sessions | Yes                               |

**Recommended for any code you write: use an `sk_live_` API key.** Mint
one at **Hub UI → Settings → API Keys**.

```http theme={null}
GET /v1/agents HTTP/1.1
Host: api.kataven.ai
Authorization: Bearer sk_live_acme_AbCd1234secretportionhere…
```

That's it — no `X-Account-ID` header needed. The backend resolves your
account from the `sk_live_` prefix.

## Hostnames

```
https://api.kataven.ai/v1/...        ← API surface (SDKs, MCP, CI)
https://hub.kataven.ai/...           ← Dashboard UI + legacy /hub-api path
```

`api.kataven.ai` is decoupled from the dashboard — outages on hub.\* don't
affect programmatic access, and rate-limit / monitoring policies apply
independently.

## Mint, list, revoke API keys

**API key management is Hub UI / Zitadel-JWT only.** A leaked
`sk_live_` cannot mint another `sk_live_`, by design — otherwise key
rotation wouldn't be a real defense. The `POST`/`PATCH`/`DELETE`
operations on `/api/v1/api-keys` return `403 Forbidden` for
`sk_live_` callers; the SDK doesn't even expose them.

In the Hub UI:

1. **Settings → API Keys → Create API key**
2. Pick a name (e.g. `ci-deploy-bot`) and an expiry (Never, 1 year, 90
   days, 30 days). Per-tenant policy may restrict the max expiry.
3. Click **Create**. The full token shows up **once** — copy it
   immediately. We don't store the plaintext, only an HMAC-SHA256
   hash with a server-side pepper.
4. Use the token in `Authorization: Bearer …`.

Revocation is instant (one-click in the same UI). A revoked key fails
on the next request, propagated within \~10 ms via Redis Pub/Sub.

## Two key types, two purposes

After the 2026-05-08 split, credentials live in two separate tables
with two separate prefixes:

| Type           | Prefix     | Where it goes                                                                              | Backend table |
| -------------- | ---------- | ------------------------------------------------------------------------------------------ | ------------- |
| **API key**    | `sk_live_` | server-side / SDK / MCP — full account access                                              | `api_keys`    |
| **Widget key** | `pk_live_` | customer HTML embed (`<script data-client-key="…">`) — public identifier, NOT a credential | `widget_keys` |

Visitor-JWT signing on your backend uses any `sk_live_` API key — no
paired `pk_live_` required. The widget key is purely an account
identifier the browser hands back to `/api/v1/widget/config`.

Both are managed at **Settings → API Keys** in the Hub UI. Widget key
CRUD is also available via the SDK (it's a public identifier, so
minting one carries no security risk).

## Per-account databases

Kataven is multi-tenant by **database isolation**, not by row-level
filtering. Each account has its own PostgreSQL database. The
`X-Account-ID` header (or, for `sk_live_` tokens, the prefix) tells the
API which one to connect to — there is no `account_id` column anywhere.

Account names are validated against a regex
(`^[a-z0-9][a-z0-9_-]{0,62}$`) — anything else is rejected at the edge.

The reserved account `kataven-admin` is rejected on regular endpoints
with `403`. It's used only for the operator console.

## Public endpoints

A small set of endpoints intentionally requires no auth:

* `GET /api/accounts` and `GET /api/accounts/validate/{account}` —
  account discovery before any token exists.
* `POST /api/v1/widget/auth/*` and `GET /api/v1/widget/config` —
  called cross-origin from customer-page widgets, before any end-user
  has a Kataven session. These have their own per-key auth
  (`pk_live_*` / `sk_live_*`) — see [Widget concepts](/concepts/widget).

## Errors

| Status | Meaning                                                         |
| ------ | --------------------------------------------------------------- |
| `400`  | Malformed token, or missing X-Account-ID for legacy JWT auth.   |
| `401`  | Missing or invalid bearer token.                                |
| `403`  | `kataven-admin` rejected, JWT account mismatch, or revoked key. |
| `404`  | Resource not found in this account's database.                  |
| `429`  | Rate limit exceeded — see [Rate limits](/concepts/rate-limits). |

## Programmatic verification

Internally, both token types go through the same path:

```
client → api.kataven.ai/v1/* → hub-api-server middleware →
   gRPC → auth-service → returns claims → handler runs
```

The middleware caches verified results for 30 seconds per token, so
repeat requests skip the gRPC hop entirely. Revocations propagate via
Redis Pub/Sub in \~10ms.

For the full architecture, see
[docs/plan/api-keys.md](https://github.com/KarthikRevanuru/kataven-hub/blob/main/docs/plan/api-keys.md).
