Per-tool keys
Create, list and revoke per-tool keys. A per-tool key is a normal kenari key that belongs to one tool on one machine, for example OpenCode on your laptop. If one tool’s config leaks, you revoke that one key and the rest keep working. The endpoints are specific to kenari and have no OpenAI or Anthropic equivalent.
POST /v1/account/keys, GET /v1/account/keys and DELETE /v1/account/keys/{id}
Who can call it
Section titled “Who can call it”Only the key that kenari login saved can manage per-tool keys. See kenari CLI. Any other key gets 403 and cli_login_key_required, including:
- A key you created under API Keys in the dashboard, and the key from
kenari login --api-key. - A per-tool key. A tool cannot create more keys for itself.
- A key from
kenari loginthat you created before per-tool keys existed. Runkenari loginagain to get one that works.
A shared API key gets shared_key_not_allowed, and a restricted key gets restricted_key_not_allowed. A key is restricted when it has a model restriction, a spend cap or a token cap. See Account overview.
Each call needs the key in the Authorization header as Bearer kn-....
Create a key
Section titled “Create a key”POST /v1/account/keys creates a key under the key you call it with.
| Field | Type | Required | Description |
|---|---|---|---|
tool | string | Yes | The tool the key is for, such as opencode. 1 to 40 characters of a-z, 0-9, . and -. |
host | string | Yes | The machine the tool runs on, such as laptop. 1 to 64 characters of A-Z, a-z, 0-9, ., _ and -. |
The response is 201 with the new key:
{ "id": "e72755dd-ea6b-4d93-9e96-eb4c817824d0", "key": "kn-984e1408144f2e604c38d27e6596638ec57882ad022a4fdd", "prefix": "kn-984e1408", "label": "kenari-cli · opencode · laptop", "created_at": 1790000000}| Field | Type | Description |
|---|---|---|
id | string | The id to use when you revoke the key. |
key | string | The full key. It is shown once, so store it now. |
prefix | string | The first characters of the key, to tell keys apart. |
label | string | kenari-cli · <tool> · <host>. The same label shows next to the key under API Keys in the dashboard. |
created_at | integer | When the key was created, in epoch seconds. |
A per-tool key works like the key it came from for every model and endpoint, except that it cannot manage keys. It follows the same billing as your other keys.
List keys
Section titled “List keys”GET /v1/account/keys takes no parameters. It returns the active per-tool keys of the key you call it with, newest first. Revoked keys are not listed, and the full key is never returned again.
{ "object": "list", "data": [ { "id": "e72755dd-ea6b-4d93-9e96-eb4c817824d0", "prefix": "kn-984e1408", "label": "kenari-cli · opencode · laptop", "created_at": 1790000000, "last_used_at": 1790003600 } ]}last_used_at is when the key last made a request, in epoch seconds. It is null when no use is recorded in the last 90 days.
Revoke a key
Section titled “Revoke a key”DELETE /v1/account/keys/{id} revokes one per-tool key for good. The next request that uses it gets a 401.
{ "id": "e72755dd-ea6b-4d93-9e96-eb4c817824d0", "revoked": true}The id must belong to a per-tool key of the key you call it with. Any other id returns 404 and not_found, whether the key does not exist, is already revoked, or belongs to someone else.
Limits
Section titled “Limits”- One login key can hold 30 active per-tool keys. Revoke one to create another.
- Per-tool keys count toward your account’s key limit, the same as any other key.
- Each login key can create 20 keys per minute.
Revoking the login key
Section titled “Revoking the login key”When you revoke the key from kenari login under API Keys in the dashboard, all its per-tool keys are revoked with it. Revoking a single per-tool key does not affect the login key or the other per-tool keys. See Authentication and API keys.
Examples
Section titled “Examples”# Create a keycurl https://kenari.id/v1/account/keys \ -H "Authorization: Bearer $KENARI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "tool": "opencode", "host": "laptop" }'
# List keyscurl https://kenari.id/v1/account/keys \ -H "Authorization: Bearer $KENARI_API_KEY"
# Revoke a keycurl -X DELETE https://kenari.id/v1/account/keys/e72755dd-ea6b-4d93-9e96-eb4c817824d0 \ -H "Authorization: Bearer $KENARI_API_KEY"Here $KENARI_API_KEY is the key from kenari login.
Python
Section titled “Python”import osimport requests
headers = {"Authorization": f"Bearer {os.environ['KENARI_API_KEY']}"}
created = requests.post( "https://kenari.id/v1/account/keys", headers=headers, json={"tool": "opencode", "host": "laptop"},)created.raise_for_status()tool_key = created.json()print(tool_key["label"])
requests.delete( f"https://kenari.id/v1/account/keys/{tool_key['id']}", headers=headers,).raise_for_status()JavaScript
Section titled “JavaScript”const headers = { Authorization: `Bearer ${process.env.KENARI_API_KEY}` };
const created = await fetch("https://kenari.id/v1/account/keys", { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ tool: "opencode", host: "laptop" }),});if (!created.ok) throw new Error(await created.text());const toolKey = await created.json();console.log(toolKey.label);
const revoked = await fetch(`https://kenari.id/v1/account/keys/${toolKey.id}`, { method: "DELETE", headers,});if (!revoked.ok) throw new Error(await revoked.text());Billing
Section titled “Billing”Creating, listing and revoking keys is free. Requests made with a per-tool key are billed like any other request. See How billing works.
Errors
Section titled “Errors”| Status | Code | When |
|---|---|---|
| 400 | bad_request | The body is not JSON with string fields tool and host, or one of them has characters or a length that is not allowed. Also when your account is at its key limit, with the message key limit reached. |
| 401 | None | The key is missing, invalid, revoked or expired. The body is a short plain-text message, not JSON. |
| 403 | cli_login_key_required | The key is not the one from kenari login. |
| 403 | shared_key_not_allowed | The key is a shared API key. |
| 403 | restricted_key_not_allowed | The key has a model restriction, a spend cap or a token cap. |
| 404 | not_found | On revoke, the id is not an active per-tool key of the key you called with. |
| 409 | child_key_cap | On create, the login key already holds 30 active per-tool keys. |
| 429 | rate_limit_exceeded | On create, more than 20 requests in one minute. There is no Retry-After header. Wait a few seconds and retry. |
| 500 | internal_error | Something failed in kenari. Retry once. |
See Errors for the error format and the remaining codes.