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.
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);
import osfrom kernel import Kernelclient = 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)
package mainimport ( "context" "fmt" "github.com/kernel/kernel-go-sdk")func main() { ctx := context.Background() client := kernel.NewClient() apiKey, err := client.APIKeys.New(ctx, kernel.APIKeyNewParams{ Name: "staging-ci", DaysToExpire: kernel.Int(30), ProjectID: kernel.String("proj_staging_9f3k"), }) if err != nil { panic(err) } fmt.Println(apiKey.Key) // Save this value now. Kernel won't show it again. fmt.Println(apiKey.ID, apiKey.MaskedKey)}
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.
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 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.
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 for every action and what each operation requires.
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 revokes connection URLs (CDP, WebDriver, live view) the key already obtained. They keep working, including for new connections, until the browser is deleted, so delete the key’s browsers to cut off access.
Rotating a scoped key keeps its policy.
Scoped keys can’t manage API keys, and they get 403 on endpoints that don’t support scoped access yet.
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.Key points about deployment keys:
One key per deployment. Each deploy (including a redeploy of the same app) mints a fresh deployment key. KERNEL_API_KEY is a reserved environment variable — a value you supply at deploy time is overridden by the injected key. To use your own long-lived key, pass it under a non-reserved name.
Lifecycle tied to the deployment. A deployment key stays valid while its deployment is active. When you redeploy, the new deployment supersedes the old one, and the old deployment’s key is released once it is no longer needed — that is, once the superseded deployment is stopped and no invocation is still running on it.
In-flight invocations are drained, not cut off. If an invocation is still running on a deployment that gets superseded, its key is kept valid until that invocation completes; the key is released right after. An idle redeploy (nothing in flight) releases the old key immediately. In the rare case where an invocation’s workflow terminates without releasing the key, a background sweep releases it after a grace period (~95 minutes). You do not need to manage any of this — it is automatic.
If you need a credential whose lifetime you control (for CI, a persistent backend, or a resource that should outlive any single deployment), create an org- or project-scoped key as described below and pass it explicitly to the client. This only affects the calls your app makes — the platform still injects the deployment key and uses it to run the invocation and report its result, so you’re choosing the credential for your own calls, not replacing the deployment 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.
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);
Rename a key when the owner or purpose changes. Delete a key when the workload no longer needs access.
An API key cannot delete itself. Authenticate with a different key when deleting a key, so the request cannot revoke the credential that is authorizing it.
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:
days_to_expire sets the new key’s lifetime in days (1-3650). Omit it to give the new key the same lifetime the rotated key originally had, or to never expire if the old key never did.
expire_in_days sets how long the old key keeps working before it expires. Use 0 to revoke it immediately, or omit it for the default 7-day grace window. The old key stops authenticating automatically once the window passes—you don’t need to delete it.
const rotated = await kernel.apiKeys.rotate('key_01jwv4tn5m8k3q2v7x9p0a1bc2', { days_to_expire: 30, expire_in_days: 7,});console.log(rotated.key); // Save this value now. Kernel won't show it again.console.log(rotated.id, rotated.masked_key);
import osfrom kernel import Kernelclient = Kernel(api_key=os.environ["KERNEL_API_KEY"])rotated = client.api_keys.rotate( "key_01jwv4tn5m8k3q2v7x9p0a1bc2", days_to_expire=30, expire_in_days=7,)print(rotated.key) # Save this value now. Kernel won't show it again.print(rotated.id, rotated.masked_key)
package mainimport ( "context" "fmt" "github.com/kernel/kernel-go-sdk")func main() { ctx := context.Background() client := kernel.NewClient() rotated, err := client.APIKeys.Rotate( ctx, "key_01jwv4tn5m8k3q2v7x9p0a1bc2", kernel.APIKeyRotateParams{ DaysToExpire: kernel.Int(30), ExpireInDays: kernel.Int(7), }, ) if err != nil { panic(err) } fmt.Println(rotated.Key) // Save this value now. Kernel won't show it again. fmt.Println(rotated.ID, rotated.MaskedKey)}
After you rotate a key:
Store the new plaintext key in your secret manager.
Deploy or restart the workload that uses KERNEL_API_KEY.
Verify the workload can call Kernel before the grace window ends.
To cut over immediately instead of using a grace window, pass expire_in_days: 0 so the old key stops working as soon as the new one is issued.