> ## Documentation Index
> Fetch the complete documentation index at: https://tbd-6fc993ce-feature-cookbooks-skills-banner.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Credential Items

> Collect and update encrypted credentials, then fill selected fields in a vault-attached browser

use a `credential` item for usernames, passwords, totp generators, and other non-payment credentials. it belongs directly to a [vault](/vaults/overview); you don't need a wallet or an external credential provider.

<Warning>
  use `wallet` and `card` items for credit card numbers, security codes, and expiration dates. don't store, collect, or fill payment-card data through credential items.
</Warning>

credential items store values and support explicit [browser fill](/vaults/fill). your application or agent still handles navigation, submission, and the site's response. choose [managed auth](/auth/overview) instead when you want KERNEL to run login flows and maintain authenticated sessions.

## Define an item

create a vault for each end user, then define their credential item. keep your api key and credential writes in your trusted backend.

vault limits are organization-wide and depend on your plan. run `kernel org entitlements -o json` and inspect `limits.max_vaults` before choosing a per-user allocation strategy; `null` means unlimited. see [organization entitlements](/reference/cli/org#kernel-org-entitlements).

<CodeGroup>
  ```typescript TypeScript theme={null}
  import Kernel from "@onkernel/sdk";

  const kernel = new Kernel({
    projectID: process.env.KERNEL_PROJECT_ID,
  });
  const vault = await kernel.vaults.upsert({ name: "user-12345" });
  const item = await kernel.vaults.items.upsert("portal-login", {
    id_or_name: vault.id,
    type: "credential",
    spec: {
      description: "Account Portal",
      fields: {
        username: { type: "email", required: true, sensitive: false },
        password: { type: "password", required: true, sensitive: true },
      },
    },
  });
  ```

  ```python Python theme={null}
  import os

  from kernel import Kernel

  kernel = Kernel(
      project_id=os.environ["KERNEL_PROJECT_ID"],
  )
  vault = kernel.vaults.upsert(name="user-12345")
  item = kernel.vaults.items.upsert(
      "portal-login",
      id_or_name=vault.id,
      type="credential",
      spec={
          "description": "Account Portal",
          "fields": {
              "username": {"type": "email", "required": True, "sensitive": False},
              "password": {"type": "password", "required": True, "sensitive": True},
          },
      },
  )
  ```
</CodeGroup>

use only the recognizable site or service name for `description`. it's the collection form's title, not a destination restriction. choose a vault per user or access boundary; don't put unrelated users' credentials in the same vault.

| field setting | behavior                                                                                                                                                   |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`        | `text`, `email`, `password`, or `totp`                                                                                                                     |
| `required`    | defaults to `true`; all required fields must have values for the item to become ready                                                                      |
| `sensitive`   | defaults to `true`; leave it `true` for write-only values, or set it `false` only when your backend or collection form needs to read and prefill the value |
| `value`       | optional initial value from trusted code; omit it to leave the field unset                                                                                 |

password and totp fields must be sensitive. usernames and email addresses can also remain sensitive; `fill` works either way. fields marked `sensitive: false` return their stored values in item responses.

an item accepts 1–32 named fields. field names must start with an ascii letter, contain only ascii letters, numbers, and underscores, and be at most 64 characters. `email` values must be bare valid addresses such as `user@example.com`; use `text` for usernames that aren't valid email addresses. initial values must be nonempty strings and fit within 16 kib of utf-8 data each; `null` and empty strings aren't accepted on creation.

field names, types, required flags, and sensitivity are immutable. repeating the same creation request retrieves the current item without overwriting later edits. use an update for value changes; use a new item key for a different field definition.

### Copy values from an existing vault

if your application already stores a username and password in another vault,
read them from your trusted backend and include each `value` in the initial
`upsert`. this creates a ready item without opening a collection form. the
example uses aws secrets manager, but the same flow applies to another vault.

today, this operation copies the values into KERNEL rather than creating a live
connection to the source vault. KERNEL encrypts and stores the copy. your
existing vault can remain the source of truth, but changes there don't
automatically update the credential item.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import {
    GetSecretValueCommand,
    SecretsManagerClient,
  } from "@aws-sdk/client-secrets-manager";

  const secrets = new SecretsManagerClient({ region: "us-east-1" });
  const source = await secrets.send(
    new GetSecretValueCommand({ SecretId: "production/account-portal" }),
  );
  if (!source.SecretString) {
    throw new Error("account portal credential is unavailable");
  }
  const credential = JSON.parse(source.SecretString) as Record<string, unknown>;
  if (
    typeof credential.username !== "string" ||
    typeof credential.password !== "string" ||
    !credential.username ||
    !credential.password
  ) {
    throw new Error("account portal credential is incomplete");
  }

  const item = await kernel.vaults.items.upsert("portal-login", {
    id_or_name: vault.id,
    type: "credential",
    spec: {
      description: "Account Portal",
      fields: {
        username: {
          type: "email",
          required: true,
          sensitive: true,
          value: credential.username,
        },
        password: {
          type: "password",
          required: true,
          sensitive: true,
          value: credential.password,
        },
      },
    },
  });
  if (item.type !== "credential" || item.state.status !== "ready") {
    throw new Error("credential is not ready");
  }
  ```

  ```python Python theme={null}
  import json

  import boto3

  secrets = boto3.client("secretsmanager", region_name="us-east-1")
  source = secrets.get_secret_value(SecretId="production/account-portal")
  credential = json.loads(source["SecretString"])
  if (not isinstance(credential.get("username"), str) or
          not isinstance(credential.get("password"), str) or
          not credential["username"] or not credential["password"]):
      raise RuntimeError("account portal credential is incomplete")

  item = kernel.vaults.items.upsert(
      "portal-login",
      id_or_name=vault.id,
      type="credential",
      spec={
          "description": "Account Portal",
          "fields": {
              "username": {
                  "type": "email",
                  "required": True,
                  "sensitive": True,
                  "value": credential["username"],
              },
              "password": {
                  "type": "password",
                  "required": True,
                  "sensitive": True,
                  "value": credential["password"],
              },
          },
      },
  )
  if item.type != "credential" or item.state.status != "ready":
      raise RuntimeError("credential is not ready")
  ```
</CodeGroup>

keep direct values in trusted backend variables and request bodies. don't put
them in frontend code, agent prompts, command-line arguments, logs, or traces.
when the source credential rotates, use an [authenticated
update](#read-and-update-values) to copy the changed fields into the KERNEL item.
repeating the initial `upsert` retrieves the existing item without overwriting
its values.

`fill` always operates on the ready credential item. a future provider
integration might back that item with a third-party vault connection, similar
to a card item backed by a third-party wallet connection. third-party credential
backing isn't currently available, so copying values is required today.
continue with [fill browser fields](/vaults/fill) after the item is ready.

## Collect values from the user

if required values are missing, the item has `state.status: "pending_collection"` and an `action` named `collect`. present `action.url` in your authenticated application, bound to the intended user and item. open the url as returned; don't extract its token or pass it to the public item api.

<Warning>
  collection links are bearer credentials. share them only with the intended user, not in shared logs, analytics, screenshots, or replay. in an application, present the link directly in the user's authenticated interface. the [cli cookbook](/browsers/use-vault-credentials-in-browser-agent) lets an agent relay it in a private conversation. open collection outside the agent-controlled browser.
</Warning>

KERNEL-hosted links expire after 30 minutes. retrieve the item from your trusted backend to obtain a renewed link when needed; an expired link can't renew itself. listing items doesn't renew collection sessions.

poll the item with a bounded wait until its status is `ready`. readiness means required values exist, not that a website accepted them. optional fields can remain unset, and unset fields can't be filled.

invoke the advertised `collect` operation to reopen collection for a ready item. this doesn't clear its values. record its `version` before opening the form, then retrieve without `wait` to observe edits: `wait` waits for readiness, not changes to an already-ready item. a version change can also come from an api update, so it doesn't identify a particular form submission.

the form includes text, email, and password fields, not totp. non-sensitive values can be prefilled; stored secrets aren't revealed. required visible inputs must be populated on submission.

## Read and update values

`spec.fields` contains definitions, never initial values. `state.fields` reports `has_value` for each field. it also returns `value` when the field is populated and `sensitive: false`. sensitive values aren't returned.

updates require `type: "credential"` and the latest `version`. send only fields you want to change. bind forms to the immutable item id with `expected_item_id` so a deleted and recreated key can't receive an old form's submission.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const replacementPassword = process.env.NEW_PORTAL_PASSWORD;
  if (!replacementPassword) {
    throw new Error("a replacement password is required");
  }

  const current = await kernel.vaults.items.retrieve(item.key, { id_or_name: vault.id });
  if (current.type !== "credential" || current.id !== item.id) {
    throw new Error("credential item was replaced");
  }
  const updated = await kernel.vaults.items.update(item.key, {
    id_or_name: vault.id,
    type: "credential",
    version: current.version,
    expected_item_id: item.id,
    spec: { fields: { password: { value: replacementPassword } } },
  });
  ```

  ```python Python theme={null}
  replacement_password = os.environ["NEW_PORTAL_PASSWORD"]

  current = kernel.vaults.items.retrieve(item.key, id_or_name=vault.id)
  if current.type != "credential" or current.id != item.id:
      raise RuntimeError("credential item was replaced")
  updated = kernel.vaults.items.update(
      item.key,
      id_or_name=vault.id,
      type="credential",
      version=current.version,
      expected_item_id=item.id,
      spec={"fields": {"password": {"value": replacement_password}}},
  )
  ```
</CodeGroup>

this replaces the password without returning either value. public updates can instead clear text, email, and password values with `null` or `""`, even when required; clearing a required value returns the item to `pending_collection`. required totp seeds can't be cleared. this differs from the hosted form, which requires populated required inputs.

a successful update increments the version and invalidates outstanding hosted collection sessions. on a `409` conflict, reload and reconcile with the user rather than automatically resubmitting stale edits.

## Use totp

supply a base32 generator seed through trusted backend code, using `value` at creation or an authenticated update. a required totp field needs its seed at creation; the collection form can't supply it. an optional totp field can receive its seed later.

KERNEL generates a fresh six-digit code immediately before filling, using hmac-sha1 and a 30-second period. neither the seed nor a generated code is returned by item reads. the seed never enters the browser; the generated code does. custom algorithms, periods, and digit counts aren't supported.

## Build your own collection surface

your authenticated backend can retrieve safe item state and submit value updates using the ordinary item api. authenticate your own users, authorize their access to the vault and immutable item id, and apply the version precondition. keep broad KERNEL api credentials out of the frontend.

render every form-supported field, omit totp, prefill only non-sensitive values, and don't log submitted values. for an unchanged form, use your own completion callback rather than an empty update. KERNEL doesn't store or authenticate your custom collection url.

continue with [fill browser fields](/vaults/fill) or the [browser agent cookbook](/browsers/use-vault-credentials-in-browser-agent).
