From 79d377a885078f340b38238689df64bdd92f4df5 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:45:10 +0000 Subject: [PATCH 1/3] Document scoped API keys --- info/api-keys.mdx | 126 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 3 deletions(-) diff --git a/info/api-keys.mdx b/info/api-keys.mdx index 8d7ae74b..b1e1bff3 100644 --- a/info/api-keys.mdx +++ b/info/api-keys.mdx @@ -88,6 +88,123 @@ func main() { ``` +## Scope a key to specific resources + +By default, an API key can do anything in its organization or project. Add a `policy` when you create a key to limit it to specific actions on specific resources. A key with a policy is denied everything the policy doesn't grant. + +Use scoped keys when you hand a credential to code you trust less than your backend, such as an AI agent, a customer-facing worker, or a short-lived job. Keys created without `policy` work exactly as before. + +### Write a policy + +A policy is a list of allow statements. Each statement pairs one `action` with one `resource`: + +| Resource | Matches | +| --- | --- | +| `organizations/{org_id}/*` | Everything in the organization. | +| `projects/{project_id}/*` | Everything in the project. | +| `projects/{project_id}/{kind}/{id}` | One browser, profile, vault, or proxy, for example `projects/proj_staging_9f3k/profiles/prof_7h2kq9x4m1`. | +| `{kind}/{id}` | One object in the key's project, or anywhere in the organization for an org-scoped key. | + +`kind` is `browsers`, `profiles`, `vaults`, or `proxies`. Kernel checks every exact ID when you create the key. IDs that don't exist return `404`, and resources outside the key's organization or project return `400`. + +| Action | Allows | +| --- | --- | +| `browsers:create` | Create browsers. Grant it on a project or organization, not an ID. | +| `browsers:use` | Inspect, connect to, and control browsers. | +| `browsers:write` | Update browser configuration. | +| `browsers:delete` | Delete browsers. | +| `profiles:create` / `profiles:read` | Create profiles / view profile metadata. | +| `profiles:use` | Attach profiles to browsers and export them. | +| `profiles:write` / `profiles:delete` | Rename profiles and save browser state into them / delete profiles. | +| `vaults:create` / `vaults:read` | Create vaults / view vault and item metadata. | +| `vaults:use` | Fill credentials and run vault operations. | +| `vaults:write` / `vaults:delete` | Create, update, and delete vault items / delete vaults. | +| `proxies:create` / `proxies:read` | Create proxies / view proxy configuration. | +| `proxies:use` | Select saved proxies for browsers and run health checks. | +| `proxies:write` / `proxies:delete` | Rename proxies / delete proxies. | +| `projects:read` / `organizations:read` | View project or organization metadata and limits. | + +Resources a browser references need their own grants: + +- **Creating a browser** needs `browsers:create` and `browsers:use` on its project or organization, because the response includes connection URLs. +- **Profiles:** attaching a profile needs `profiles:use` on it. Saving changes back to it also needs `profiles:write`. +- **Vaults:** linking a vault needs `vaults:use` on every vault. +- **Proxies:** selecting a saved proxy needs `proxies:use` on it. +- **Existing browsers:** a key can only use a browser if it can also use that browser's attached profile and vaults. + +List endpoints return only the resources the key can access, so a key without a matching grant gets an empty list rather than an error. A profile appears in `GET /profiles` only with `profiles:read`, so grant it alongside `profiles:use` if your code looks profiles up by listing them. + +### Create a scoped key + +This key can create and control browsers in one project, only with profile `prof_7h2kq9x4m1` or no profile, and it expires in one hour: + + +```typescript TypeScript +import Kernel from '@onkernel/sdk'; + +const kernel = new Kernel({ + apiKey: process.env.KERNEL_API_KEY, +}); + +const project = 'projects/proj_staging_9f3k'; + +const apiKey = await kernel.apiKeys.create({ + name: 'checkout-agent-run-4812', + project_id: 'proj_staging_9f3k', + expires_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(), + policy: { + statements: [ + { action: 'browsers:create', resource: `${project}/*` }, + { action: 'browsers:use', resource: `${project}/*` }, + { action: 'profiles:use', resource: `${project}/profiles/prof_7h2kq9x4m1` }, + ], + }, +}); + +console.log(apiKey.key); // Save this value now. Kernel won't show it again. +console.log(apiKey.policy); +``` + +```python Python +import os +from datetime import datetime, timedelta, timezone + +from kernel import Kernel + +client = Kernel(api_key=os.environ["KERNEL_API_KEY"]) + +project = "projects/proj_staging_9f3k" + +api_key = client.api_keys.create( + name="checkout-agent-run-4812", + project_id="proj_staging_9f3k", + expires_at=datetime.now(timezone.utc) + timedelta(hours=1), + policy={ + "statements": [ + {"action": "browsers:create", "resource": f"{project}/*"}, + {"action": "browsers:use", "resource": f"{project}/*"}, + {"action": "profiles:use", "resource": f"{project}/profiles/prof_7h2kq9x4m1"}, + ], + }, +) + +print(api_key.key) # Save this value now. Kernel won't show it again. +print(api_key.policy) +``` + + +To limit a key to vault `vlt_3m8q2r7k1x` instead, grant `vaults:use` on `projects/proj_staging_9f3k/vaults/vlt_3m8q2r7k1x`. Add `vaults:read` if the key also needs to read the vault's metadata. + +### Expiry and revocation + +- `expires_at` sets an exact expiry time, so a key can live for minutes rather than whole days. It can't be combined with `days_to_expire`. +- A scoped key can't outlive the credential that creates it. If your key expires, set `expires_at` or `days_to_expire` no later than that. +- An expired key is rejected immediately. A deleted key is rejected within about 10 seconds. +- Neither closes browser connections that are already open. Delete those browsers to cut off access immediately. +- Rotating a scoped key gives the replacement the same policy. + +Scoped keys can't create, list, or delete API keys, and they get `403` with `insufficient_scope` on any endpoint that doesn't support scoped access yet. Manage keys with an unscoped key. + ## Deployment API keys When you deploy an app, Kernel mints a **deployment-scoped API key** for that deployment and injects it into the deployment (and every invocation it runs) as the `KERNEL_API_KEY` environment variable. Because the SDKs read `KERNEL_API_KEY` from the environment by default, your app can call the Kernel API as itself without you managing a key. @@ -102,7 +219,7 @@ If you need a credential whose lifetime you control (for CI, a persistent backen ## 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. +List keys to audit what exists. List and retrieve responses include `masked_key`, `project_id`, `project_name`, `created_by`, expiry metadata, and `policy` for scoped keys, but they don't include the plaintext key. ```typescript TypeScript @@ -228,7 +345,7 @@ func main() { ## Rotate a key -`rotate` issues a replacement key in a single call and keeps the old key working for a short grace period, so your workload can switch over without downtime. The new key copies the rotated key's name and project scope, and—like create—Kernel returns the plaintext `key` only once. +`rotate` issues a replacement key in a single call and keeps the old key working for a short grace period, so your workload can switch over without downtime. The new key copies the rotated key's name, project scope, and policy, and—like create—Kernel returns the plaintext `key` only once. Two optional parameters control the timing: @@ -308,5 +425,8 @@ To cut over immediately instead of using a grace window, pass `expire_in_days: 0 | --- | --- | --- | | `400 Bad Request` | The name is missing, `days_to_expire` is outside `1`-`3650`, `expire_in_days` is outside `0`-`3650`, or `project_id` is empty. | Send a name, choose a valid expiry, or omit `project_id` for an org-scoped key. | | `400 Bad Request` (rotate) | `days_to_expire` is shorter than `expire_in_days`, so the new key would expire before the old key's grace window ends. | Raise `days_to_expire` or lower `expire_in_days`. | +| `400 Bad Request` (policy) | A statement uses an unknown action, a malformed resource, or a resource outside the key's organization or project. | Check the action and resource against [Write a policy](#write-a-policy). A project-scoped key can't be granted `organizations/{org_id}/*`. | +| `400 Bad Request` (expiry) | `expires_at` is in the past or combined with `days_to_expire`, or a scoped key would outlive the credential creating it. | Send one expiry field, no later than your own key's expiry. | | `401 Unauthorized` | Kernel couldn't authenticate the request. | Set a valid `KERNEL_API_KEY`. | -| `404 Not Found` | The project or API key doesn't exist, or the caller can't access it. | Check the ID. If you're using a project-scoped key, you can only rotate keys in that same project. | +| `403 Forbidden` (`insufficient_scope`) | A scoped key is missing a permission or called an endpoint scoped keys can't use yet. | The message names the missing action and resource. Add it to the policy, or use an unscoped key. | +| `404 Not Found` | The project, API key, or a resource named in the policy doesn't exist, or the caller can't access it. | Check the ID. If you're using a project-scoped key, you can only rotate keys in that same project. | From aa0abe6b2dba6dc606c173e6b5c8ddb0f152ab4b Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Fri, 9 Oct 2026 19:49:33 +0000 Subject: [PATCH 2/3] Define scoped key actions once and tighten the section --- info/api-keys.mdx | 88 ++++++++++++++++------------------------------- 1 file changed, 29 insertions(+), 59 deletions(-) diff --git a/info/api-keys.mdx b/info/api-keys.mdx index b1e1bff3..3f0eba08 100644 --- a/info/api-keys.mdx +++ b/info/api-keys.mdx @@ -90,53 +90,34 @@ func main() { ## Scope a key to specific resources -By default, an API key can do anything in its organization or project. Add a `policy` when you create a key to limit it to specific actions on specific resources. A key with a policy is denied everything the policy doesn't grant. +By default, an API key can do anything in its organization or project. Add a `policy` when you create a key to limit it to specific actions on specific resources. The key is denied everything the policy doesn't grant. Keys created without `policy` work exactly as before. -Use scoped keys when you hand a credential to code you trust less than your backend, such as an AI agent, a customer-facing worker, or a short-lived job. Keys created without `policy` work exactly as before. +Use scoped keys when you hand a credential to code you trust less than your backend, such as an AI agent or a short-lived job. ### Write a policy -A policy is a list of allow statements. Each statement pairs one `action` with one `resource`: +A policy is a list of statements, each pairing an `action` with a `resource`. -| Resource | Matches | -| --- | --- | -| `organizations/{org_id}/*` | Everything in the organization. | -| `projects/{project_id}/*` | Everything in the project. | -| `projects/{project_id}/{kind}/{id}` | One browser, profile, vault, or proxy, for example `projects/proj_staging_9f3k/profiles/prof_7h2kq9x4m1`. | -| `{kind}/{id}` | One object in the key's project, or anywhere in the organization for an org-scoped key. | +A resource is one of: + +- `projects/{project_id}/*` or `organizations/{org_id}/*` for everything in a project or organization. +- `projects/{project_id}/{kind}/{id}` for one object, where `kind` is `browsers`, `profiles`, `vaults`, or `proxies`. -`kind` is `browsers`, `profiles`, `vaults`, or `proxies`. Kernel checks every exact ID when you create the key. IDs that don't exist return `404`, and resources outside the key's organization or project return `400`. +An action is `{kind}:{verb}`, for example `browsers:use`: -| Action | Allows | +| Verb | Allows | | --- | --- | -| `browsers:create` | Create browsers. Grant it on a project or organization, not an ID. | -| `browsers:use` | Inspect, connect to, and control browsers. | -| `browsers:write` | Update browser configuration. | -| `browsers:delete` | Delete browsers. | -| `profiles:create` / `profiles:read` | Create profiles / view profile metadata. | -| `profiles:use` | Attach profiles to browsers and export them. | -| `profiles:write` / `profiles:delete` | Rename profiles and save browser state into them / delete profiles. | -| `vaults:create` / `vaults:read` | Create vaults / view vault and item metadata. | -| `vaults:use` | Fill credentials and run vault operations. | -| `vaults:write` / `vaults:delete` | Create, update, and delete vault items / delete vaults. | -| `proxies:create` / `proxies:read` | Create proxies / view proxy configuration. | -| `proxies:use` | Select saved proxies for browsers and run health checks. | -| `proxies:write` / `proxies:delete` | Rename proxies / delete proxies. | -| `projects:read` / `organizations:read` | View project or organization metadata and limits. | - -Resources a browser references need their own grants: - -- **Creating a browser** needs `browsers:create` and `browsers:use` on its project or organization, because the response includes connection URLs. -- **Profiles:** attaching a profile needs `profiles:use` on it. Saving changes back to it also needs `profiles:write`. -- **Vaults:** linking a vault needs `vaults:use` on every vault. -- **Proxies:** selecting a saved proxy needs `proxies:use` on it. -- **Existing browsers:** a key can only use a browser if it can also use that browser's attached profile and vaults. - -List endpoints return only the resources the key can access, so a key without a matching grant gets an empty list rather than an error. A profile appears in `GET /profiles` only with `profiles:read`, so grant it alongside `profiles:use` if your code looks profiles up by listing them. +| `create` | Create objects of that kind. Grant it on a project or organization, not an ID. Creating a browser also needs `browsers:use`, because the response includes connection URLs. | +| `read` | View metadata. Lists only include objects the key can read, or for browsers, use. | +| `use` | Control a browser, attach a profile, fill from a vault, or select a saved proxy. Using a browser also needs `use` on its attached profile and vaults. | +| `write` | Update an object. For profiles, this includes saving browser state into them. For vaults, it covers creating, updating, and deleting items. | +| `delete` | Delete an object. | + +Browsers support every verb except `read`. `projects:read` and `organizations:read` view project and organization metadata. ### Create a scoped key -This key can create and control browsers in one project, only with profile `prof_7h2kq9x4m1` or no profile, and it expires in one hour: +This key can create and control browsers in one project, only with profile `prof_7h2kq9x4m1` or no profile, and expires in one hour: ```typescript TypeScript @@ -146,23 +127,20 @@ const kernel = new Kernel({ apiKey: process.env.KERNEL_API_KEY, }); -const project = 'projects/proj_staging_9f3k'; - const apiKey = await kernel.apiKeys.create({ name: 'checkout-agent-run-4812', project_id: 'proj_staging_9f3k', expires_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(), policy: { statements: [ - { action: 'browsers:create', resource: `${project}/*` }, - { action: 'browsers:use', resource: `${project}/*` }, - { action: 'profiles:use', resource: `${project}/profiles/prof_7h2kq9x4m1` }, + { action: 'browsers:create', resource: 'projects/proj_staging_9f3k/*' }, + { action: 'browsers:use', resource: 'projects/proj_staging_9f3k/*' }, + { action: 'profiles:use', resource: 'projects/proj_staging_9f3k/profiles/prof_7h2kq9x4m1' }, ], }, }); console.log(apiKey.key); // Save this value now. Kernel won't show it again. -console.log(apiKey.policy); ``` ```python Python @@ -173,37 +151,30 @@ from kernel import Kernel client = Kernel(api_key=os.environ["KERNEL_API_KEY"]) -project = "projects/proj_staging_9f3k" - api_key = client.api_keys.create( name="checkout-agent-run-4812", project_id="proj_staging_9f3k", expires_at=datetime.now(timezone.utc) + timedelta(hours=1), policy={ "statements": [ - {"action": "browsers:create", "resource": f"{project}/*"}, - {"action": "browsers:use", "resource": f"{project}/*"}, - {"action": "profiles:use", "resource": f"{project}/profiles/prof_7h2kq9x4m1"}, + {"action": "browsers:create", "resource": "projects/proj_staging_9f3k/*"}, + {"action": "browsers:use", "resource": "projects/proj_staging_9f3k/*"}, + {"action": "profiles:use", "resource": "projects/proj_staging_9f3k/profiles/prof_7h2kq9x4m1"}, ], }, ) print(api_key.key) # Save this value now. Kernel won't show it again. -print(api_key.policy) ``` -To limit a key to vault `vlt_3m8q2r7k1x` instead, grant `vaults:use` on `projects/proj_staging_9f3k/vaults/vlt_3m8q2r7k1x`. Add `vaults:read` if the key also needs to read the vault's metadata. - ### Expiry and revocation -- `expires_at` sets an exact expiry time, so a key can live for minutes rather than whole days. It can't be combined with `days_to_expire`. -- A scoped key can't outlive the credential that creates it. If your key expires, set `expires_at` or `days_to_expire` no later than that. -- An expired key is rejected immediately. A deleted key is rejected within about 10 seconds. -- Neither closes browser connections that are already open. Delete those browsers to cut off access immediately. -- Rotating a scoped key gives the replacement the same policy. +- Use `expires_at` for an exact expiry, such as minutes from now, instead of `days_to_expire`. A scoped key can't outlive the key that creates it. +- An expired key is rejected immediately, and a deleted key within about 10 seconds. Neither closes browser connections that are already open, so delete those browsers to cut off access. +- Rotating a scoped key keeps its policy. -Scoped keys can't create, list, or delete API keys, and they get `403` with `insufficient_scope` on any endpoint that doesn't support scoped access yet. Manage keys with an unscoped key. +Scoped keys can't manage API keys, and they get `403` on endpoints that don't support scoped access yet. ## Deployment API keys @@ -425,8 +396,7 @@ To cut over immediately instead of using a grace window, pass `expire_in_days: 0 | --- | --- | --- | | `400 Bad Request` | The name is missing, `days_to_expire` is outside `1`-`3650`, `expire_in_days` is outside `0`-`3650`, or `project_id` is empty. | Send a name, choose a valid expiry, or omit `project_id` for an org-scoped key. | | `400 Bad Request` (rotate) | `days_to_expire` is shorter than `expire_in_days`, so the new key would expire before the old key's grace window ends. | Raise `days_to_expire` or lower `expire_in_days`. | -| `400 Bad Request` (policy) | A statement uses an unknown action, a malformed resource, or a resource outside the key's organization or project. | Check the action and resource against [Write a policy](#write-a-policy). A project-scoped key can't be granted `organizations/{org_id}/*`. | -| `400 Bad Request` (expiry) | `expires_at` is in the past or combined with `days_to_expire`, or a scoped key would outlive the credential creating it. | Send one expiry field, no later than your own key's expiry. | +| `400 Bad Request` (scoped key) | The policy has an unknown action or a resource outside the key's organization or project, or the expiry is invalid or later than your own key's. | Check the policy against [Write a policy](#write-a-policy) and send one expiry field. | | `401 Unauthorized` | Kernel couldn't authenticate the request. | Set a valid `KERNEL_API_KEY`. | -| `403 Forbidden` (`insufficient_scope`) | A scoped key is missing a permission or called an endpoint scoped keys can't use yet. | The message names the missing action and resource. Add it to the policy, or use an unscoped key. | +| `403 Forbidden` (`insufficient_scope`) | A scoped key is missing a permission. | The message names the missing action and resource. Add it to the policy. | | `404 Not Found` | The project, API key, or a resource named in the policy doesn't exist, or the caller can't access it. | Check the ID. If you're using a project-scoped key, you can only rotate keys in that same project. | From b45520225669216cb3a1fa2e9e4450b5aa09ac39 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Fri, 9 Oct 2026 19:56:01 +0000 Subject: [PATCH 3/3] Move scoped key permissions to a reference page --- docs.json | 1 + info/api-key-permissions.mdx | 85 ++++++++++++++++++++++++++++++++++++ info/api-keys.mdx | 26 +++-------- 3 files changed, 93 insertions(+), 19 deletions(-) create mode 100644 info/api-key-permissions.mdx diff --git a/docs.json b/docs.json index 567cde9e..3e33ce15 100644 --- a/docs.json +++ b/docs.json @@ -266,6 +266,7 @@ "introduction/manage", "info/projects", "info/api-keys", + "info/api-key-permissions", "info/audit-logs", "info/network-access" ] diff --git a/info/api-key-permissions.mdx b/info/api-key-permissions.mdx new file mode 100644 index 00000000..4333580f --- /dev/null +++ b/info/api-key-permissions.mdx @@ -0,0 +1,85 @@ +--- +title: "API Key Permissions" +description: "Actions and resources you can grant in a scoped API key policy" +--- + +This page lists everything you can grant in a scoped API key's `policy`. To create a scoped key, see [Scope a key to specific resources](/info/api-keys#scope-a-key-to-specific-resources). + +## Resources + +| Resource | Matches | +| --- | --- | +| `organizations/{org_id}/*` | Everything in the organization. | +| `projects/{project_id}/*` | Everything in the project. | +| `projects/{project_id}/{kind}/{id}` | One object. `kind` is `browsers`, `profiles`, `vaults`, or `proxies`. | + +Resources must be inside the new key's organization, or its project for a project-scoped key. Kernel checks that every object ID exists when you create the key. + +## Actions + +### Browsers + +| Action | Allows | +| --- | --- | +| `browsers:create` | Create browsers. Grant it on a project or organization. | +| `browsers:use` | List, inspect, connect to, and control browsers. | +| `browsers:write` | Update browser configuration. | +| `browsers:delete` | Delete browsers. | + +### Profiles + +| Action | Allows | +| --- | --- | +| `profiles:create` | Create profiles. Grant it on a project or organization. | +| `profiles:read` | List profiles and view their metadata. | +| `profiles:use` | Attach profiles to browsers and download them. | +| `profiles:write` | Rename profiles and save browser state into them. | +| `profiles:delete` | Delete profiles. | + +### Vaults + +| Action | Allows | +| --- | --- | +| `vaults:create` | Create vaults. Grant it on a project or organization. | +| `vaults:read` | List vaults and view vault and item metadata. | +| `vaults:use` | Link vaults to browsers, fill credentials, and run vault item operations. | +| `vaults:write` | Create, update, and delete vault items. | +| `vaults:delete` | Delete vaults and their items. | + +### Proxies + +| Action | Allows | +| --- | --- | +| `proxies:create` | Create proxies. Grant it on a project or organization. | +| `proxies:read` | List proxies and view their configuration, without credentials. | +| `proxies:use` | Select saved proxies for browsers and run health checks. | +| `proxies:write` | Rename proxies. | +| `proxies:delete` | Delete proxies. | + +### Projects and organizations + +| Action | Allows | +| --- | --- | +| `projects:read` | View project metadata and limits. | +| `organizations:read` | View organization entitlements and limits. | + +## Operations that need more than one action + +| Operation | Requires | +| --- | --- | +| Create a browser | `browsers:create` and `browsers:use` on the project or organization, because the response includes connection URLs. Add `profiles:use` on its profile, `profiles:write` if it saves profile changes, `vaults:use` on each linked vault, and `proxies:use` on a saved proxy. | +| Any operation on an existing browser | The browser action, plus `profiles:use` on its attached profile and `vaults:use` on every linked vault. If the browser saves profile changes, `profiles:write` on that profile too. | +| Update a browser | `browsers:write` and `browsers:use`, plus `use` on any profile, vault, or proxy the update adds. | +| Fill from a vault | `vaults:use` on the vault and `browsers:use` on the browser. | +| Run a proxy health check | `proxies:use` and `proxies:read`. | + +List endpoints return only the objects the key can access, so a key without a matching grant gets an empty list. + +## Not available to scoped keys + +Scoped keys get `403` with `insufficient_scope` on: + +- API key management, including creating, listing, rotating, and deleting keys. +- Browser pools. +- Creating browsers that use saved extensions, telemetry export, or app invocations. +- Any other endpoint not covered by the actions above. diff --git a/info/api-keys.mdx b/info/api-keys.mdx index 3f0eba08..92f2daf2 100644 --- a/info/api-keys.mdx +++ b/info/api-keys.mdx @@ -90,30 +90,18 @@ func main() { ## Scope a key to specific resources -By default, an API key can do anything in its organization or project. Add a `policy` when you create a key to limit it to specific actions on specific resources. The key is denied everything the policy doesn't grant. Keys created without `policy` work exactly as before. +By default, an API key can do anything in its organization or project. Add a `policy` when you create a key to limit what it can do. Keys created without `policy` work exactly as before. Use scoped keys when you hand a credential to code you trust less than your backend, such as an AI agent or a short-lived job. -### Write a policy +### How policies work -A policy is a list of statements, each pairing an `action` with a `resource`. +A policy is a list of **statements**. Each statement allows one **action** on one **resource**, and the key is denied everything no statement allows. -A resource is one of: +- A **resource** is either one object, `projects/{project_id}/{kind}/{id}`, or everything in a container, `projects/{project_id}/*` or `organizations/{org_id}/*`. +- An **action** is `{kind}:{verb}`, such as `browsers:use` or `vaults:read`. -- `projects/{project_id}/*` or `organizations/{org_id}/*` for everything in a project or organization. -- `projects/{project_id}/{kind}/{id}` for one object, where `kind` is `browsers`, `profiles`, `vaults`, or `proxies`. - -An action is `{kind}:{verb}`, for example `browsers:use`: - -| Verb | Allows | -| --- | --- | -| `create` | Create objects of that kind. Grant it on a project or organization, not an ID. Creating a browser also needs `browsers:use`, because the response includes connection URLs. | -| `read` | View metadata. Lists only include objects the key can read, or for browsers, use. | -| `use` | Control a browser, attach a profile, fill from a vault, or select a saved proxy. Using a browser also needs `use` on its attached profile and vaults. | -| `write` | Update an object. For profiles, this includes saving browser state into them. For vaults, it covers creating, updating, and deleting items. | -| `delete` | Delete an object. | - -Browsers support every verb except `read`. `projects:read` and `organizations:read` view project and organization metadata. +Some operations need more than one action. For example, creating a browser with a profile needs `browsers:create` and `browsers:use` on the project, plus `profiles:use` on that profile. See [API key permissions](/info/api-key-permissions) for every action and what each operation requires. ### Create a scoped key @@ -396,7 +384,7 @@ To cut over immediately instead of using a grace window, pass `expire_in_days: 0 | --- | --- | --- | | `400 Bad Request` | The name is missing, `days_to_expire` is outside `1`-`3650`, `expire_in_days` is outside `0`-`3650`, or `project_id` is empty. | Send a name, choose a valid expiry, or omit `project_id` for an org-scoped key. | | `400 Bad Request` (rotate) | `days_to_expire` is shorter than `expire_in_days`, so the new key would expire before the old key's grace window ends. | Raise `days_to_expire` or lower `expire_in_days`. | -| `400 Bad Request` (scoped key) | The policy has an unknown action or a resource outside the key's organization or project, or the expiry is invalid or later than your own key's. | Check the policy against [Write a policy](#write-a-policy) and send one expiry field. | +| `400 Bad Request` (scoped key) | The policy has an unknown action or a resource outside the key's organization or project, or the expiry is invalid or later than your own key's. | Check the policy against [API key permissions](/info/api-key-permissions) and send one expiry field. | | `401 Unauthorized` | Kernel couldn't authenticate the request. | Set a valid `KERNEL_API_KEY`. | | `403 Forbidden` (`insufficient_scope`) | A scoped key is missing a permission. | The message names the missing action and resource. Add it to the policy. | | `404 Not Found` | The project, API key, or a resource named in the policy doesn't exist, or the caller can't access it. | Check the ID. If you're using a project-scoped key, you can only rotate keys in that same project. |