# Authentication and API keys

Every `/v1` endpoint except `GET /v1/models` needs an API key. A key starts with `kn-`, and one key works for every model and every API format.

## Create a key

Open **API Keys** in the dashboard and select **Create API Key**. Add a **Label (optional)**, such as the name of the app that will use the key, so you can tell keys apart and revoke one without touching the others.

kenari shows the key once, right after you create it. It cannot be recovered later. If you lose a key, select **Rotate key** on its row to get a new one, or create a new key and revoke the old one.

## Send the key

Send the key in one of two headers. Both work on every `/v1` endpoint.

| Header | Used by |
| --- | --- |
| `Authorization: Bearer kn-...` | OpenAI SDKs and most HTTP clients |
| `x-api-key: kn-...` | Anthropic SDKs and Claude Code |

If a request has both headers, `Authorization` wins. Send the bare key in `x-api-key`, without a `Bearer` prefix.

```bash
curl https://kenari.id/v1/chat/completions \
  -H "Authorization: Bearer $KENARI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "step-3-7-flash:free",
    "messages": [{"role": "user", "content": "Hello!"}]
  }'
```

## Sign in from a terminal

The [kenari CLI](/en/docs/cli) can create and store a key for you. Install it with `npm install -g @kenarihq/cli`, then run `kenari login`. It opens your browser to approve the sign-in, then saves a new key, so you never copy it by hand. On a server with no browser, create a key in the dashboard and run `kenari login --api-key`. It prompts for the key, or reads it from stdin when you pipe it in. Never pass the key as an argument. The guide above has the details.

## Call from a browser

The `/v1` API can be called straight from browser JavaScript, so a `fetch` from a web page works without a proxy of your own.

```js
const response = await fetch("https://kenari.id/v1/chat/completions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "step-3-7-flash:free",
    messages: [{ role: "user", content: "Hello!" }],
  }),
});
const data = await response.json();
console.log(data.choices[0].message.content);
```

`/v1` answers requests from any origin and allows `GET`, `POST` and `OPTIONS`. It accepts whatever request headers the browser asks for in its preflight, so `Authorization`, `Content-Type`, `x-api-key` and the Anthropic headers all pass. Cookies are not used: `/v1` reads only the `Authorization` or `x-api-key` header. Scripts can read the `x-request-id` and `request-id` response headers. The dashboard API under `/api` stays closed to cross-origin requests.

<aside class="warn"><strong>Warning</strong> Anyone who opens the browser's developer tools can read a key that a web page sends. Use this for local tools, personal projects and prototypes. A production web app should keep the key on a server and call kenari from there, or use a restricted shared API key.</aside>

## Public endpoints

`GET /v1/models` is the only `/v1` endpoint that works without a key. These endpoints are public:

| Endpoint | Purpose |
| --- | --- |
| `GET /v1/models` | The model list, including `?modality=` filters. See [Models and pricing](/en/docs/models). |
| `GET /api/public/pricing` | Current prices and free-tier limits |
| `GET /openapi.json` | The OpenAPI description |

A request without a key, or with a bad one, gets a `401` with a plain-text body instead of JSON. See [Errors](/en/docs/errors#plain-text-responses) for the messages.

## Manage and revoke keys

**API Keys** lists every key with its status, **Active** or **Revoked**. Select **Revoke key** to disable a key for good. A revoked key cannot be turned back on, and the next request that uses it gets a `401`. If you revoke the key that `kenari login` saved, its [per-tool keys](/en/docs/account-keys) are revoked with it. **Rotate key** creates a new key and revokes the old one in one step, so update your app to the new key right after.

A key with the **MCP connection** badge belongs to a client that signed in to the [MCP server](/en/docs/mcp). Revoke it to disconnect that client. It has no **Rotate key**, because the client holds its own sign-in, not the key.

kenari checks the account on every request. If an account is suspended, every key on it stops working at once, and creating a new key does not help.

## Restrict a key

Open **Advanced settings** in the create dialog to narrow what a key can do.

| Setting | What it does |
| --- | --- |
| **Expiry** | The key stops working on that date. Choose **Never expires**, a number of days, or **Custom**. |
| **Model scope** | **All models**, or **Specific models**. A request for any other model fails with `400` and `bad_request`, and the message lists the allowed models. |
| **Paid capabilities** | Which paid features the key can use: **Document reading**, **Web search and page fetching**, and **X search**. |
| **Spend cap, Rp (optional)** | The most the key can spend, counted at catalog prices and not against your balance. When it is reached, requests fail with `402` and `insufficient_balance`. |

The capability list works as follows. A key with no model restriction can use every paid capability. A key with a model scope can use only the capabilities you tick, and a request for another one fails with `400` and `bad_request`, with no charge. **Document reading** gates OCR only, meaning the `ocr` engine and `/v1/ocr`. A restricted key can still attach documents that kenari reads the default way, because those are ordinary input tokens of a model it may use. See [Read documents](/en/docs/documents) and [Web and X search](/en/docs/web-search).

A **Token cap (optional)**, the total of prompt and completion tokens, is set when you create a shared API key.

A key with a model scope, a spend cap or a token cap cannot use named routes. A key with a spend cap also cannot use BYOK models that you address as `byok/...`, and the request fails with `403` and `byok_spend_capped_key`. A spend cap counts the Rupiah kenari records, and BYOK usage records Rp 0, so the cap would never be reached. To limit BYOK usage on a key, use a token cap. See [Providers](/en/docs/byok).

A restricted key also reads only its own usage from `GET /v1/usage`, and `GET /v1/account/balance` and `GET /v1/account/quota` refuse it with `403` and `restricted_key_not_allowed`. See [Account overview](/en/docs/account).

## Workspace access

A key can also manage a team workspace through the [Workspace API](/en/docs/workspace-api). No key has this until you turn it on. An Owner or Admin of a team sets it when creating a key: open **API Keys**, select **Create API Key**, open **Advanced settings**, and choose one of four options under **Workspace access**.

| Option | What it allows |
| --- | --- |
| **None** | This key only calls models. |
| **Read** | Reads the workspace's members, invites and seats. |
| **Manage** | Also invites, changes roles, removes members and sets PAYG limits. |
| **Manage + payments** | Also buys and changes seats, and moves personal balance to the workspace. |

At **Manage + payments**, the dialog also asks for the **Daily limit for moving personal balance**. It is the most the key can move from the Personal balance of the person who created it into the workspace, counted over a rolling 24 hours. The default is Rp 5,000,000. The lowest value is Rp 0, which means the key cannot move Personal balance, and the highest is Rp 100,000,000.

The level and the daily limit cannot be changed later. To change them, revoke the key and create a new one. **Rotate key** keeps both.

A key with workspace access cannot have a model restriction, a capability restriction or a spend cap. A shared API key and the keys that `kenari login` creates never have workspace access.

## Shared API keys

A shared API key lets other people call kenari without an account of their own. Open **Share**, find **Share an API key**, and select **Create a new share**. Choose the **Shared models**, an **Expiry (1-90 days)**, and optionally a spend cap, a token cap, a note, and the paid capabilities. Then select **Create Share**. kenari creates a public link and a key. The key is shown once, like any other key.

A shared key spends from your balance or plan, only on the models you chose, and stops on the expiry date or when a cap is reached. When your own balance cannot cover a request, the holder gets a `402` and `insufficient_balance` that points at you, the owner. Revoke a share from the same page with **Revoke**, and the link and key stop working at once.

A shared key is a limited grant, so it cannot do these things:

- Read your account data. `GET /v1/account/balance`, `GET /v1/account/quota` and the [Usage](/en/docs/usage) endpoints refuse a shared key with `403` and `shared_key_not_allowed`, and the `kenari_balance`, `kenari_usage` and `kenari_quota` tools of the [MCP server](/en/docs/mcp) refuse it too. See [Account quota](/en/docs/account-quota).
- Reuse a document reading that another key paid for. The request fails with `400` and the document is not read. Uploading the same document again does not get around this, because the stored reading stays tied to the key that paid for it. See [Read documents](/en/docs/documents).
- Use named routes.

Shared keys also have their own pacing. See [Rate limits](/en/docs/rate-limits).

## See also

- [Quickstart](/en/docs/quickstart)
- [Providers](/en/docs/byok)
- [Rate limits](/en/docs/rate-limits)
- [Errors](/en/docs/errors)
