diff --git a/docs.json b/docs.json
index 763e3d5..5991021 100644
--- a/docs.json
+++ b/docs.json
@@ -113,6 +113,7 @@
"auth/faq"
]
},
+ "info/api-keys",
"browsers/file-io",
"browsers/curl",
"browsers/ssh",
@@ -256,6 +257,7 @@
"reference/cli/browsers",
"reference/cli/apps",
"reference/cli/projects",
+ "reference/cli/api-keys",
"reference/cli/mcp",
"reference/cli/extensions"
]
diff --git a/info/api-keys.mdx b/info/api-keys.mdx
new file mode 100644
index 0000000..20194ed
--- /dev/null
+++ b/info/api-keys.mdx
@@ -0,0 +1,135 @@
+---
+title: "API Keys"
+description: "Create, scope, rotate, and delete Kernel API keys"
+---
+
+An API key is the credential your server, script, or CI job uses to call Kernel without an interactive login. Treat it like a password: keep it out of client-side code, store it in a secret manager, and rotate it when access changes.
+
+Kernel only returns the plaintext key once, when you create it. Save the `key` value immediately. After that, Kernel only shows the masked value.
+
+## Before you start
+
+You need one existing Kernel credential to create another API key:
+
+- Set `KERNEL_API_KEY` before running the SDK examples.
+
+API keys can be **org** or **project** scoped:
+
+- Omit `project_id` to create an org-scoped key that can access resources across your organization.
+- Set `project_id` to create a project-scoped key that can only access resources in that project.
+- When you authenticate with a project-scoped key, you can only create another project-scoped key for the same project.
+
+## Create an API key
+
+Use the SDKs when your backend needs to provision keys for environments, customers, or automation jobs.
+
+### SDKs
+
+
+```typescript TypeScript
+import Kernel from '@onkernel/sdk';
+
+const kernel = new Kernel({
+ apiKey: process.env.KERNEL_API_KEY,
+});
+
+const apiKey = await kernel.apiKeys.create({
+ name: 'staging-ci',
+ days_to_expire: 30,
+ project_id: 'proj_staging_9f3k',
+});
+
+console.log(apiKey.key); // Save this value now. Kernel won't show it again.
+console.log(apiKey.id, apiKey.masked_key);
+```
+
+```python Python
+import os
+from kernel import Kernel
+
+client = Kernel(api_key=os.environ["KERNEL_API_KEY"])
+
+api_key = client.api_keys.create(
+ name="staging-ci",
+ days_to_expire=30,
+ project_id="proj_staging_9f3k",
+)
+
+print(api_key.key) # Save this value now. Kernel won't show it again.
+print(api_key.id, api_key.masked_key)
+```
+
+
+## List and inspect API keys
+
+List keys to audit what exists. List and retrieve responses include `masked_key`, `project_id`, `project_name`, `created_by`, and expiry metadata, but they don't include the plaintext key.
+
+
+```typescript TypeScript
+for await (const apiKey of kernel.apiKeys.list({ limit: 20 })) {
+ console.log(apiKey.id, apiKey.name, apiKey.masked_key);
+}
+
+const apiKey = await kernel.apiKeys.retrieve('key_01jwv4tn5m8k3q2v7x9p0a1bc2');
+console.log(apiKey.project_id, apiKey.expires_at);
+```
+
+```python Python
+import os
+from kernel import Kernel
+
+client = Kernel(api_key=os.environ["KERNEL_API_KEY"])
+
+for api_key in client.api_keys.list(limit=20):
+ print(api_key.id, api_key.name, api_key.masked_key)
+
+api_key = client.api_keys.retrieve("key_01jwv4tn5m8k3q2v7x9p0a1bc2")
+print(api_key.project_id, api_key.expires_at)
+```
+
+
+## Rename or delete an API key
+
+Rename a key when the owner or purpose changes. Delete a key when the workload no longer needs access.
+
+
+```typescript TypeScript
+await kernel.apiKeys.update('key_01jwv4tn5m8k3q2v7x9p0a1bc2', {
+ name: 'staging-ci-rotated',
+});
+
+await kernel.apiKeys.delete('key_01jwv4tn5m8k3q2v7x9p0a1bc2');
+```
+
+```python Python
+import os
+from kernel import Kernel
+
+client = Kernel(api_key=os.environ["KERNEL_API_KEY"])
+
+client.api_keys.update(
+ "key_01jwv4tn5m8k3q2v7x9p0a1bc2",
+ name="staging-ci-rotated",
+)
+
+client.api_keys.delete("key_01jwv4tn5m8k3q2v7x9p0a1bc2")
+```
+
+
+## Rotate a key
+
+Rotate by creating the replacement first, then deleting the old key after your workload has switched over.
+
+1. Create a new API key with the same scope.
+2. Store the new plaintext key in your secret manager.
+3. Deploy or restart the workload that uses `KERNEL_API_KEY`.
+4. Verify the workload can call Kernel.
+5. Delete the old API key.
+
+## Troubleshooting
+
+| Error | What it means | What to do |
+| --- | --- | --- |
+| `400 Bad Request` | The name is missing, `days_to_expire` is outside `1`-`3650`, or `project_id` is empty. | Send a name, choose a valid expiry, or omit `project_id` for an org-scoped key. |
+| `401 Unauthorized` | Kernel couldn't authenticate the request. | Set a valid `KERNEL_API_KEY`. |
+| `404 Not Found` | The project doesn't exist or the caller can't access it. | Check the project ID. If you're using a project-scoped key, create keys only for that same project. |
diff --git a/info/projects.mdx b/info/projects.mdx
index 2a42164..9693db7 100644
--- a/info/projects.mdx
+++ b/info/projects.mdx
@@ -79,7 +79,7 @@ other = kernel.browsers.create(
API keys can be **org-wide** or **project-scoped**.
- **Existing API keys are org-wide.** They see every resource in your organization across all projects. Include an `X-Kernel-Project-Id` header to restrict a single request to one project.
-- **Project-scoped API keys** can only access resources inside the project they were issued for. Create one from the **API Keys** page in the dashboard and pick the target project when generating the key. Requests made with a scoped key are automatically limited to that project — no header required. If you do send an `X-Kernel-Project-Id` header and it conflicts with the key's project, the request is rejected with `403 Forbidden`.
+- **Project-scoped API keys** can only access resources inside the project they were issued for. Create one from the **API Keys** page in the dashboard, the [CLI](/reference/cli/api-keys), an SDK, or the [API keys guide](/info/api-keys), and pass the target `project_id` when generating the key. Requests made with a scoped key are automatically limited to that project — no header required. If you do send an `X-Kernel-Project-Id` header and it conflicts with the key's project, the request is rejected with `403 Forbidden`.
### OAuth
diff --git a/reference/cli.mdx b/reference/cli.mdx
index 5c3c4f4..edcb503 100644
--- a/reference/cli.mdx
+++ b/reference/cli.mdx
@@ -46,6 +46,9 @@ kernel --version
Manage projects and scope commands with `--project`.
+
+ Create, list, rename, and delete API keys.
+
## Quick Start
diff --git a/reference/cli/api-keys.mdx b/reference/cli/api-keys.mdx
new file mode 100644
index 0000000..f1e0ce3
--- /dev/null
+++ b/reference/cli/api-keys.mdx
@@ -0,0 +1,83 @@
+---
+title: "API Keys"
+---
+
+Manage [API keys](/info/api-keys) from the CLI.
+
+## `kernel api-keys create`
+
+Create an API key. By default, the new key is org-wide. Pass `--project-id` to create a key whose own access is scoped to that project.
+
+```bash
+kernel api-keys create \
+ --name staging-ci \
+ --days-to-expire 30 \
+ --project-id proj_staging_9f3k \
+ --output json
+```
+
+| Flag | Description |
+|------|-------------|
+| `--name ` | API key name. Required. |
+| `--days-to-expire ` | Number of days until expiry, from `1` to `3650`. Omit for no expiry. |
+| `--project-id ` | Create a project-scoped API key for this project. Omit for org-wide. |
+| `--output json`, `-o json` | Output the raw JSON object, including the plaintext `key` on create. |
+
+
+ `--project-id` controls the access scope of the new API key. The global `--project` flag only scopes the CLI request you're making.
+
+
+## `kernel api-keys list`
+
+List API keys in the authenticated organization. API keys are masked.
+
+```bash
+kernel api-keys list --limit 20
+```
+
+| Flag | Description |
+|------|-------------|
+| `--limit ` | Maximum number of results to return. |
+| `--offset ` | Number of results to skip. |
+| `--output json`, `-o json` | Output the raw JSON array. |
+
+## `kernel api-keys get `
+
+Show one API key by ID. The response includes the masked key and metadata, not the plaintext key.
+
+```bash
+kernel api-keys get key_01jwv4tn5m8k3q2v7x9p0a1bc2
+```
+
+| Flag | Description |
+|------|-------------|
+| `--output json`, `-o json` | Output the raw JSON object. |
+
+## `kernel api-keys update `
+
+Rename an API key.
+
+```bash
+kernel api-keys update key_01jwv4tn5m8k3q2v7x9p0a1bc2 --name staging-ci-rotated
+```
+
+| Flag | Description |
+|------|-------------|
+| `--name ` | New API key name. Required. |
+| `--output json`, `-o json` | Output the raw JSON object. |
+
+## `kernel api-keys delete `
+
+Delete an API key.
+
+```bash
+kernel api-keys delete key_01jwv4tn5m8k3q2v7x9p0a1bc2 --yes
+```
+
+| Flag | Description |
+|------|-------------|
+| `--yes`, `-y` | Skip the confirmation prompt. |
+
+## Aliases
+
+You can also use `kernel api-key`, `kernel apikeys`, or `kernel apikey`.
diff --git a/reference/cli/auth.mdx b/reference/cli/auth.mdx
index 12a889c..23b2771 100644
--- a/reference/cli/auth.mdx
+++ b/reference/cli/auth.mdx
@@ -19,10 +19,10 @@ Display authentication status, including the active user, organization, and toke
Set the `KERNEL_API_KEY` environment variable to authenticate without OAuth:
```bash
-export KERNEL_API_KEY=
+export KERNEL_API_KEY=sk_1234abcd
```
-Create and manage API keys from the Kernel dashboard.
+Create and manage API keys from the Kernel dashboard or with [`kernel api-keys`](/reference/cli/api-keys).
## Global flags
The following flags are available on every CLI command:
@@ -36,4 +36,3 @@ The following flags are available on every CLI command:
## Getting help
- `kernel --help` — Show the top-level command list.
- `kernel --help` — Display command-specific usage and options.
-