diff --git a/browsers/enable-payments-in-browser-agent.mdx b/browsers/enable-payments-in-browser-agent.mdx
new file mode 100644
index 0000000..dd073c2
--- /dev/null
+++ b/browsers/enable-payments-in-browser-agent.mdx
@@ -0,0 +1,639 @@
+---
+title: "Enable Payments in a Browser Agent"
+description: "Give a browser agent provider-backed payment aliases and complete a web checkout without exposing card data"
+---
+
+you're building a browser agent. you can add payments without bringing your application, agent, or browser into pci dss scope.
+
+create a KERNEL vault and add a wallet backed by [stripe link](/integrations/payments/stripe-link) or [agentcard](/integrations/payments/agentcard). the provider-hosted flow collects and stores the user's payment method, so neither you nor your agent handles the card number or cvc. attach the vault when you create a browser session, give the agent the returned aliases, and let it complete a web checkout. KERNEL handles authorization and payment handoff at egress.
+
+you can use either [stripe link](/integrations/payments/stripe-link) or
+[agentcard](/integrations/payments/agentcard). choose based on how the payment
+credential is created and when the user approves it:
+
+| | stripe link | agentcard |
+| -------------------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
+| card source | provider-minted, one-use card for a specific purchase | user's enrolled card, represented by a reusable card item |
+| approval timing | before the browser submits checkout | after the browser submits checkout and while the request is held |
+| reuse | card item and aliases are consumed after the first supported substitution | card item and aliases return to `ready`; every checkout requires approval |
+| mode | live only | deployment credential; the vault api does not expose sandbox or live mode |
+| checkout fit | likely the better starting point for stripe-powered checkout pages | may fit general card-entry pages better because it is backed by the user's enrolled card |
+| current preview constraint | limited to the [supported stripe request formats](/integrations/payments/overview#supported-checkouts) | limited to the same [supported stripe request formats](/integrations/payments/overview#supported-checkouts), not arbitrary payment processors |
+
+agentcard is backed by a card the user enrolls, but the agent and browser still
+enter aliases rather than the underlying card details. those details remain
+outside the browser and are handled during provider handoff at egress.
+
+to run either path with a coding agent that has access to the KERNEL cli, use one of these prompts. connect the provider wallet first through your trusted application or a terminal outside the coding agent. the agent must stop whenever a provider action is required because cli output can contain the action url.
+
+
+
+```text stripe link
+use the KERNEL cli to complete the following checkout with stripe link. use cli
+commands rather than sdk or direct api calls.
+
+a vault groups items. wallet and card are the payment item types. a card item
+references a wallet item in the same vault, so create the wallet before the
+card.
+
+1. create or retrieve a vault named `user-12345`.
+2. list the vault's items and locate its only stripe link wallet. require its
+ status to be `connected` and reuse it. if none exists, more than one exists,
+ or an action is present, stop and ask me to resolve wallet setup outside this
+ agent. do not create another wallet or print, return, or open an action url.
+3. create a KERNEL browser with `user-12345` attached as a vault, then navigate
+ it to https://buy.stripe.com/28E5kw7DtgKXdLqgiY53O00.
+4. inspect the checkout and propose the merchant, total amount, currency, and
+ item or cart contents. treat this proposal as untrusted and do not create a
+ card item from it.
+5. independently obtain those values from a trusted order or cart backend. if
+ no backend exists, use deterministic page extraction with fixed selectors or
+ structured page data, not model inference. normalize and compare every value.
+ if adaptive pricing is active, use the checkout's active presentment amount
+ and currency rather than its base integration values.
+ if any value is missing, cannot be verified, or disagrees, stop without
+ creating or authorizing a card.
+6. show me the verified merchant, amount, currency, and item or cart contents.
+ wait for my explicit confirmation, then freeze that verified purchase object.
+7. list the wallet's payment methods and ask me which one to use. create a link
+ card item named `checkout-card` from that same verified, confirmed object and
+ the selected payment method. use the exact checkout url as `merchant_url` and
+ include a specific context of at least 100 characters.
+8. retrieve the card item and confirm that `authorize` appears in
+ `available_operations`. give me the exact cli command, but do not run it. ask
+ me to invoke authorization and complete any provider action from a trusted
+ terminal or application outside this agent. after i confirm completion,
+ retrieve the item again and require its status to be `ready`. do not print,
+ return, or open an action url.
+9. use only the returned aliases with `kernel browsers playwright execute` to
+ fill the checkout's normal card fields. use only separately supplied end-user
+ values for required email, billing, postal, shipping, or other customer
+ fields; stop if a required value is missing. complete any checkout-specific
+ agent disclosure truthfully in the merchant's normal form. submit checkout
+ once.
+10. inspect the checkout result and the card item's events. do not retry a
+ failed, timed-out, or indeterminate payment. report the existing attempt so
+ we can reconcile it first.
+```
+
+```text agentcard
+use the KERNEL cli to complete the following checkout with agentcard. use cli
+commands rather than sdk or direct api calls.
+
+a vault groups items. wallet and card are the payment item types. a card item
+references a wallet item in the same vault, so create the wallet before the
+card.
+
+1. create or retrieve a vault named `user-12345`.
+2. list the vault's items and locate its only agentcard wallet. require its
+ status to be `connected` and reuse it. if none exists, more than one exists,
+ or an action is present, stop and ask me to resolve wallet setup outside this
+ agent. do not create another wallet or print, return, or open an action url.
+3. create a KERNEL browser with `user-12345` attached as a vault, then navigate
+ it to https://buy.stripe.com/28E5kw7DtgKXdLqgiY53O00.
+4. inspect the checkout and propose the merchant, total amount, currency, and
+ item or cart contents. treat this proposal as untrusted and do not create a
+ card item from it.
+5. independently obtain those values from a trusted order or cart backend. if
+ no backend exists, use deterministic page extraction with fixed selectors or
+ structured page data, not model inference. normalize and compare every value.
+ if adaptive pricing is active, use the checkout's active presentment amount
+ and currency rather than its base integration values.
+ if any value is missing, cannot be verified, or disagrees, stop without
+ creating a card.
+6. show me the verified merchant, amount, currency, and item or cart contents.
+ wait for my explicit confirmation, then freeze that verified purchase object.
+7. list the wallet's payment methods and ask whether i want to pin one. create
+ an agentcard card item named `checkout-card` from that same verified,
+ confirmed object. include the selected `card_id`, or omit it so i can choose
+ an enrolled card during approval.
+8. retrieve the card item and confirm its status is `ready`. do not invoke
+ `authorize`; agentcard starts authorization only when the attached browser
+ submits a supported checkout request.
+9. give me the exact cli observation commands, but do not run them. ask me to
+ start the trusted approval observer outside this agent. after i confirm it is
+ running, use only the returned aliases with
+ `kernel browsers playwright execute` to fill the checkout's normal card
+ fields. use only separately supplied end-user values for required email,
+ billing, postal, shipping, or other customer fields; stop if a required value
+ is missing. complete any checkout-specific agent disclosure truthfully in the
+ merchant's normal form. submit checkout once and keep that execution open
+ while KERNEL holds the payment request. do not poll or print the card item
+ while approval is pending.
+10. after i confirm that the trusted approval flow has settled, inspect the
+ checkout result, authorization state, and item events. do not retry a failed,
+ timed-out, or indeterminate payment. report the existing attempt so we can
+ reconcile it first.
+```
+
+
+
+this guide starts after you have an existing browser agent. it changes how you prepare and pass payment input, not how your agent navigates or reasons about the checkout page.
+
+## Before you start
+
+- install a KERNEL sdk version that includes the `vaults` resource.
+- set `KERNEL_API_KEY` and `KERNEL_PROJECT_ID` in the trusted controller that creates your browser.
+- use a supported stripe checkout you control. the initial release supports the request formats listed in the [payments overview](/integrations/payments/overview#supported-checkouts).
+- use a low-value checkout you control while validating the flow. stripe link card creation is live-only. agentcard mode comes from the integration's configured credential.
+- for agentcard, keep an application-owned `AGENTCARD_MODE` deployment setting and fail closed unless it explicitly matches the sandbox or live environment you intend to use. the vault api does not return this mode.
+
+
+ keep wallet collection and payment approval outside the agent. show hosted
+ action urls only in a trusted user-facing surface. the only payment values you
+ give the agent are the aliases returned in card state.
+
+
+## 1. Create a vault
+
+scope the client and vault to the same project that will own the browser session.
+
+
+
+```typescript TypeScript
+import Kernel from "@onkernel/sdk";
+
+const projectID = process.env.KERNEL_PROJECT_ID;
+if (!projectID) throw new Error("set KERNEL_PROJECT_ID");
+
+const kernel = new Kernel({ projectID, maxRetries: 0 });
+const vault = await kernel.vaults.upsert({ name: "user-12345" });
+```
+
+```python Python
+import os
+
+from kernel import Kernel
+
+kernel = Kernel(
+ project_id=os.environ["KERNEL_PROJECT_ID"],
+ max_retries=0,
+)
+vault = kernel.vaults.upsert(name="user-12345")
+```
+
+```bash CLI
+kernel vaults create --name user-12345
+```
+
+
+
+`vaults.upsert` creates the vault or retrieves an existing vault with the same name. vault names are immutable within the project.
+
+## 2. Connect the end user's payment method and prepare a card item
+
+`wallet` and `card` are KERNEL api resources, not the underlying wallet or card.
+your end user initiates setup and purchases through your product. your
+application then makes the authenticated api calls on their behalf. a card item
+represents the payment authorization and aliases for a purchase; creating one
+does not add or expose the end user's real card.
+
+the end user creates the purchase intent. your application translates that
+confirmed intent into a KERNEL card item without exposing its api key to the
+user or agent.
+
+| actor | responsibility |
+| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
+| your application | enforces one wallet per provider; independently verifies purchase values; calls KERNEL; presents provider actions; observes state; attaches the vault |
+| your end user | initiates wallet setup and purchases; adds or selects the real payment method; confirms the verified purchase; completes approval |
+| your browser agent | proposes purchase values; after verification and confirmation, receives aliases, enters them into the checkout form, and submits once |
+| KERNEL | creates the requested vault resources, returns aliases, enforces browser bindings, and handles payment data at egress |
+| the payment provider | hosts connection, enrollment, and approval; stores the underlying payment method; authorizes the purchase |
+
+### Once per end user and provider
+
+your payment settings ui must allow at most one wallet item for each provider in
+a vault. list the vault's items before rendering provider controls, group wallet
+items by `spec.provider`, and apply this flow:
+
+| existing wallet for the provider | ui behavior |
+| -------------------------------- | ------------------------------------------------------------------------------------------------------ |
+| none | show the option to connect that provider |
+| `pending_authorization` | hide the add option and resume the existing hosted action |
+| `connected` | hide the add option, show the provider as connected, and reuse the existing wallet |
+| any other state | hide the add option and show the existing state; recover it or use an explicit remove-and-replace flow |
+
+if both stripe link and agentcard wallets exist, hide both provider add options.
+recheck this condition in your trusted controller immediately before wallet
+creation so a stale ui cannot submit a second wallet.
+
+the api currently makes item keys unique, not wallet providers. using a
+different key can create another wallet for the same provider, so your product
+must enforce the one-per-provider rule. deleting a wallet invalidates its
+dependent card items; require an explicit replacement flow rather than creating
+a second wallet beside it.
+
+1. your end user chooses a provider that does not already have a wallet in the vault.
+2. your application creates or retrieves a vault for that end user, checks its items again, then creates a wallet item only when that provider is absent.
+3. your application presents the returned action in a trusted user-facing surface outside the agent-controlled browser.
+4. your end user completes the provider-hosted connection or enrollment flow.
+5. your application waits until the wallet item's status is `connected`.
+
+this is the point where your end user adds or connects their real payment
+method. the end user does not create the KERNEL wallet item directly.
+
+### Present hosted actions in your application
+
+provider action urls are bearer-like handoffs to enrollment or approval. route
+them through your trusted application:
+
+1. your backend retrieves the item and keeps the raw action url out of logs, analytics, and model context.
+2. store the action server-side under an opaque id bound to the authenticated end user, vault id, item key, and action name.
+3. render a link to your own authenticated action endpoint. before redirecting, verify the session owns that binding and the item still returns the same action.
+4. send the redirect with `Cache-Control: no-store` and `Referrer-Policy: no-referrer`.
+5. apply a short application ttl capped by `item.expires_at` or `state.authorization.expires_at` when present. invalidate the record immediately when the action changes, disappears, or reaches a terminal state.
+
+the `presentProviderAction` functions later in this guide represent this
+application-owned flow. the checkout agent and its browser must never receive
+the raw provider url.
+
+### For each purchase
+
+purchase verification is a required, fail-closed gate before creating or
+updating a card item:
+
+1. let the browser agent propose the merchant, amount, currency, and item or cart contents. treat every proposed value as untrusted.
+2. independently obtain the expected values from a trusted source. prefer your order or cart backend. when no backend exists, use deterministic page extraction with fixed selectors or structured page data, not another model response.
+3. normalize the values in trusted code and compare the proposal with the trusted result. compare the amount in minor currency units and require the merchant, currency, and item or cart contents to match.
+4. stop when any value is missing, cannot be verified, or disagrees. do not create or update a card item and do not invoke authorization.
+5. show the independently verified values to the end user and wait for explicit confirmation.
+6. freeze that verified, confirmed purchase object. derive the card specification and any authorization request from that same object. do not accept replacement values from the agent after confirmation.
+7. for stripe link, list the connected wallet's payment methods and let the end user select one. for agentcard, pin an enrolled card or let the end user choose during approval.
+8. ask KERNEL to prepare a card item on the end user's behalf. stripe link requires a new one-use item. agentcard can reuse a ready item and update its specification when the api permits. the item refers to the connected wallet; it is not the end user's real card.
+9. for stripe link, invoke the advertised `authorize` operation using the same verified purchase object, then present any returned approval action to the end user. agentcard begins authorization after the browser submits checkout.
+10. wait for the card item to become `ready`, then pass only its aliases to the browser agent.
+
+
+ never create or authorize a card from values supplied only by the browser
+ agent. end-user confirmation does not make an unverified proposal trustworthy.
+ the independently verified purchase object must be the single source for the
+ card specification, approval display, and authorization.
+
+
+for a stripe payment link without an order or cart backend, use the payment-link
+response as the deterministic source. the current response exposes
+`account_settings.display_name`, `line_item_group.total`,
+`line_item_group.currency`, and `line_item_group.line_items`. use dom text and
+`data-testid` attributes only as supplemental checks. stripe can render multiple
+responsive copies of a summary or omit product-level test ids in another layout,
+so don't require a specific test id or number of matching elements. these are
+stripe page details rather than a KERNEL contract. if the structured response is
+missing or its values disagree with the rendered checkout, fail verification
+instead of falling back to model inference.
+
+stripe adaptive pricing can change the checkout's displayed amount and currency
+for the browser's location. create the card item from the active presentment
+amount and currency shown to the user and submitted by that checkout, not the
+payment link's base integration amount and currency. include those active values
+in the verified purchase object and the confirmation screen. when the checkout
+response is your deterministic source, create the attached browser in step 3
+before you create or update the card item, then inspect and submit with that same
+browser session. the vault attachment covers items created later in that vault.
+
+
+ don't start checkout until the wallet is `connected` and the card item is
+ `ready`. without a wallet item, card creation fails because `spec.wallet` must
+ reference a wallet from the same vault and provider. with an unconnected link
+ wallet, card creation returns a conflict. with an unconnected agentcard
+ wallet, a card without `card_id` can remain `requested`, while a pinned
+ `card_id` cannot be validated. neither path is ready for checkout.
+
+
+
+ we're onboarding more native payment providers and will update this guide as
+ new integrations become available. we plan to build additional card-collection
+ and vault-management surfaces so developers can connect the payment providers
+ and third-party vaults they already use. over time, we want{" "}
+ KERNEL to support more of the
+ browser-side steps required to complete a payment without exposing the
+ underlying credential to the agent or browser.
+
+
+both provider flows leave you with a card item whose `state.status` is `ready` and whose `state.aliases` contains `number`, `cvc`, `exp_month`, and `exp_year`.
+
+### Collect non-card checkout fields
+
+payment aliases cover only the card number, cvc, and expiry. merchant checkouts
+can also require email, billing name, postal code, shipping address, phone
+number, or other customer data. collect the required values from the end user in
+your trusted application, or load values the end user has already approved from
+your backend. pass them to the browser agent separately from the card aliases.
+do not ask the agent to invent missing customer data.
+
+merchant-specific agent disclosures are normal checkout fields. if the page
+asks whether automation is acting for another person, instruct the agent to
+answer truthfully in the merchant's form before submission. for a checkout that
+offers **I am an AI agent acting on behalf of someone else**, select that option.
+don't bypass the disclosure or replace the page's normal submission with a raw
+processor request.
+
+retrieve the item immediately before creating the browser. don't cache aliases after an item expires, changes state, or is deleted.
+
+
+
+```typescript TypeScript
+const card = await kernel.vaults.items.retrieve("notebook-order", {
+ id_or_name: vault.id,
+ wait: 60,
+});
+
+if (
+ card.type !== "card" ||
+ card.state.status !== "ready" ||
+ !card.state.aliases
+) {
+ throw new Error(`payment item is ${card.state.status}`);
+}
+const aliases = card.state.aliases;
+```
+
+```python Python
+card = kernel.vaults.items.retrieve(
+ "notebook-order",
+ id_or_name=vault.id,
+ wait=60,
+)
+
+if card.type != "card" or card.state.status != "ready" or card.state.aliases is None:
+ raise RuntimeError(f"payment item is {card.state.status}")
+aliases = card.state.aliases
+```
+
+```bash CLI
+kernel vaults items get user-12345 notebook-order --wait 60 -o json
+```
+
+
+
+## 3. Attach the vault to the browser
+
+vault attachments are fixed at browser creation. use the same project-scoped client that created the vault.
+
+
+
+```typescript TypeScript
+const browser = await kernel.browsers.create({
+ vaults: [{ id: vault.id }],
+ headless: false,
+ timeout_seconds: 1800,
+});
+
+if (!browser.browser_live_view_url) {
+ throw new Error("headful browser did not return a live view url");
+}
+await presentLiveView({
+ userID: authenticatedUser.id,
+ sessionID: browser.session_id,
+ url: browser.browser_live_view_url,
+});
+```
+
+```python Python
+browser = kernel.browsers.create(
+ vaults=[{"id": vault.id}],
+ headless=False,
+ timeout_seconds=1800,
+)
+
+if browser.browser_live_view_url is None:
+ raise RuntimeError("headful browser did not return a live view url")
+present_live_view(
+ user_id=authenticated_user.id,
+ session_id=browser.session_id,
+ url=browser.browser_live_view_url,
+)
+```
+
+```bash CLI
+kernel browsers create --vault user-12345 -o json
+```
+
+
+
+`browser_live_view_url` lets the end user watch the checkout during confirmation
+and agentcard approval pauses. `presentLiveView` represents your authenticated
+application page: keep the url server-side with the end user and browser session
+binding, render or embed it only after checking that session, and remove it when
+you delete or time out the browser. don't log the url or put it in model context.
+see [live view](/browsers/live-view#embedding-in-an-iframe) for iframe and csp
+requirements.
+
+connect your existing agent to `browser.cdp_ws_url`. see [Controlling a Browser](/introduction/control) for supported connection options.
+
+## 4. Give the aliases to your agent
+
+pass the aliases as structured task input. instruct the agent to use them in the page's normal card fields and submit checkout once.
+
+```text
+Complete the checkout at https://shop.example.com for one notebook.
+The approved total is 23.06 USD.
+
+Use this payment input in the checkout form:
+- card number:
+- cvc:
+- expiry month:
+- expiry year:
+
+Use these separately collected customer fields where the checkout requires them:
+- email:
+- billing name:
+- postal code:
+
+If the checkout asks whether an agent is acting for another person, select the
+truthful disclosure option in the page before submission. For example:
+- I am an AI agent acting on behalf of someone else
+
+Submit the checkout once. If the payment request pauses for user approval,
+wait for the controller to finish that approval. Do not retry payment.
+```
+
+replace the angle-bracketed fields in your controller before sending the task. don't put oauth codes, action urls, provider responses, the vault api key, or the browser connection url in the prompt.
+
+the agent can fill both top-level fields and payment fields embedded in iframes. let the merchant page's own submission code create the outgoing request. don't replace the checkout with a raw processor api call.
+
+## 5. Keep approval and observation outside the agent
+
+start observing the card item before the agent submits checkout and keep the observer running until the merchant reaches a terminal order state. this is required for agentcard, where the outgoing checkout request is held while the user approves it. the same observer works for stripe link and provides item events after credential substitution.
+
+the example uses `presentProviderAction`, an application-owned function. it
+must publish the action only to an authenticated session for the end user who
+owns the vault. bind the action to that user, vault, and item; expire it no later
+than the item or authorization expiry; and stop serving it as soon as the action
+changes or disappears. don't put the url in application logs, analytics, model
+context, or the agent-controlled browser.
+
+
+
+```typescript TypeScript
+let after: string | undefined;
+
+async function observePayment(stop: AbortSignal): Promise {
+ while (!stop.aborted) {
+ const current = await kernel.vaults.items.retrieve(card.key, {
+ id_or_name: vault.id,
+ wait: 5,
+ });
+
+ if (current.action && "url" in current.action) {
+ await presentProviderAction({
+ userID: authenticatedUser.id,
+ vaultID: vault.id,
+ item: current,
+ });
+ } else if (current.action?.name === "push_approval") {
+ console.log("complete the approval in your wallet");
+ }
+
+ const events = await kernel.vaults.items.events(card.key, {
+ id_or_name: vault.id,
+ after,
+ wait: 5,
+ });
+ for (const event of events) {
+ console.log(event.id, event.name, event.browser_id, event.data);
+ after = event.id;
+ }
+ }
+}
+```
+
+```python Python
+from threading import Event
+
+
+def observe_payment(stop: Event) -> None:
+ after = None
+
+ while not stop.is_set():
+ current = kernel.vaults.items.retrieve(
+ card.key,
+ id_or_name=vault.id,
+ wait=5,
+ )
+
+ if current.action is not None and hasattr(current.action, "url"):
+ present_provider_action(
+ user_id=authenticated_user.id,
+ vault_id=vault.id,
+ item=current,
+ )
+ elif current.action is not None and current.action.name == "push_approval":
+ print("complete the approval in your wallet")
+
+ if after is None:
+ events = kernel.vaults.items.events(card.key, id_or_name=vault.id, wait=5)
+ else:
+ events = kernel.vaults.items.events(
+ card.key,
+ id_or_name=vault.id,
+ after=after,
+ wait=5,
+ )
+
+ for event in events:
+ print(event.id, event.name, event.browser_id, event.data)
+ after = event.id
+
+```
+
+```bash CLI
+kernel vaults items get user-12345 notebook-order --wait 5 --open
+kernel vaults items events user-12345 notebook-order --wait 60 -o json
+```
+
+
+
+start the observer before checkout and cancel it only after the checkout
+controller reaches a terminal merchant state or its reconciliation deadline.
+the following application-owned functions stand in for your existing agent and
+order backend:
+
+
+
+```typescript TypeScript
+const stop = new AbortController();
+const observer = observePayment(stop.signal);
+
+try {
+ await runBrowserAgentCheckout({ browser, aliases, verifiedPurchase });
+ await waitForMerchantResolution(verifiedPurchase.orderID);
+} finally {
+ stop.abort();
+ try {
+ await observer;
+ } finally {
+ await kernel.browsers.deleteByID(browser.session_id);
+ }
+}
+```
+
+```python Python
+from threading import Event, Thread
+
+stop = Event()
+observer = Thread(target=observe_payment, args=(stop,))
+observer.start()
+
+try:
+ run_browser_agent_checkout(
+ browser=browser,
+ aliases=aliases,
+ verified_purchase=verified_purchase,
+ )
+ wait_for_merchant_resolution(verified_purchase.order_id)
+finally:
+ stop.set()
+ try:
+ observer.join()
+ finally:
+ kernel.browsers.delete_by_id(browser.session_id)
+```
+
+
+
+if the merchant has not reached a terminal state by your controller's deadline,
+classify the attempt as indeterminate, stop the observer, retain the attempt
+identifiers, and do not submit checkout again. the observer presents user
+actions and reads events; it does not submit checkout or repeat authorization.
+
+each cli `--wait` performs one bounded observation, so rerun the commands to
+continue observing the existing attempt. CLI commands prepare and observe the
+payment state; they don't submit merchant payments or retry them. run `--open`
+only from the trusted controller or a human-operated terminal, and never send
+its output to the browser agent.
+
+`events` returns an ordered array. an empty array means no new observation arrived during the wait. it does not mean payment failed. correlate `event.browser_id` with the browser you created when a vault is attached to more than one session.
+
+## 6. Verify the outcome
+
+use item state, item events, the checkout page, and the merchant's order record together.
+
+| observation | next action |
+| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
+| `payment_succeeded` | verify that the merchant created the expected order |
+| `payment_requires_action` | continue the existing merchant flow without resubmitting payment |
+| `payment_processing` | wait for the existing payment and inspect the merchant's status |
+| stripe link `consumed`, `credential_submitted`, or `credential_tokenized` | confirm processor and merchant state; credential use alone does not prove purchase success |
+| agentcard `ready` or authorization `approved` | inspect charge, replay, and merchant state; reusable item state does not prove purchase success |
+| decline, expiry, rejection, failure, abandonment, or `payment_unknown` | stop and reconcile the existing attempt before deciding whether a new purchase is appropriate |
+
+use the merchant order record as the authority for whether the expected order
+was created and paid. require its merchant, amount, currency, and items to match
+the frozen purchase object. use authorization state and item events to explain
+the provider and handoff path, and use the checkout page as supporting evidence.
+only report success when the trusted merchant record confirms the matching
+order. a ready agentcard item, an approved authorization, a delivered replay, or
+a success page cannot establish that by itself. merchant success text is
+page-specific: a completed stripe checkout might say **Thanks for your payment**,
+but don't use that string or any other generic text matcher as the success
+condition. when the sources disagree or the merchant record is unavailable,
+keep the result indeterminate and do not retry.
+
+
+ a timeout, browser deletion, missing event, or closed checkout does not undo
+ provider execution or cancel a merchant order. don't retry automatically.
+ retain the vault id, card key, browser id, and last event id until you have
+ reconciled the attempt.
+
+
+delete the browser when the task and outcome inspection are complete. keep or delete the vault and provider items according to whether the user needs them for a future task.
diff --git a/docs.json b/docs.json
index d89c716..8ab3f21 100644
--- a/docs.json
+++ b/docs.json
@@ -25,7 +25,12 @@
{ "source": "/home", "destination": "/" },
{ "source": "/llm.md", "destination": "/integrations/stripe-projects" },
{ "source": "/llm-browser.md", "destination": "/integrations/stripe-projects-browser" },
- { "source": "/integrations/agent-browser", "destination": "/integrations/vercel/agent-browser" }
+ { "source": "/integrations/agent-browser", "destination": "/integrations/vercel/agent-browser" },
+ { "source": "/vaults/overview", "destination": "/vaults" },
+ { "source": "/integrations/payments/link", "destination": "/integrations/payments/stripe-link" },
+ { "source": "/vaults/payments", "destination": "/browsers/enable-payments-in-browser-agent" },
+ { "source": "/browsers/pay-through-browser-checkout", "destination": "/browsers/enable-payments-in-browser-agent" },
+ { "source": "/browsers/add-payments-to-browser-agent", "destination": "/browsers/enable-payments-in-browser-agent" }
],
"theme": "palm",
"appearance": {
@@ -125,6 +130,7 @@
"auth/faq"
]
},
+ "vaults",
"info/api-keys",
"info/audit-logs",
"browsers/file-io",
@@ -132,7 +138,8 @@
"browsers/ssh",
"browsers/computer-controls",
"browsers/playwright-execution",
- "browsers/playwright-computer-use-fallback"
+ "browsers/playwright-computer-use-fallback",
+ "browsers/enable-payments-in-browser-agent"
]
},
{
@@ -210,6 +217,15 @@
"integrations/computer-use/yutori"
]
},
+ {
+ "group": "Payments",
+ "icon": "/images/integration-icons/payments.svg",
+ "pages": [
+ "integrations/payments/overview",
+ "integrations/payments/stripe-link",
+ "integrations/payments/agentcard"
+ ]
+ },
"integrations/hermes-agent",
"integrations/laminar",
"integrations/stagehand",
diff --git a/images/integration-icons/payments.svg b/images/integration-icons/payments.svg
new file mode 100644
index 0000000..f110953
--- /dev/null
+++ b/images/integration-icons/payments.svg
@@ -0,0 +1,6 @@
+
diff --git a/integrations/overview.mdx b/integrations/overview.mdx
index b009657..6b01907 100644
--- a/integrations/overview.mdx
+++ b/integrations/overview.mdx
@@ -42,6 +42,7 @@ Kernel provides detailed guides for popular agent frameworks:
- **[Computer Use (OpenAGI)](/integrations/computer-use/openagi)** - OpenAGI's computer use capability
- **[Computer Use (Yutori)](/integrations/computer-use/yutori)** - Yutori Navigator n1.5 pixels-to-actions model
- **[Laminar](/integrations/laminar)** - Observability and tracing for AI browser automations
+- **[Payments](/integrations/payments/overview)** - Add provider-backed payments to browser agents without exposing card data
- **[Val Town](/integrations/valtown)** - Serverless function runtime
- **[Stripe Projects](/integrations/stripe-projects)** - Provision Kernel plans and API keys via the Stripe Projects CLI
- **[Vercel](https://github.com/onkernel/vercel-template)** - Deploy browser automations to Vercel
diff --git a/integrations/payments/agentcard.mdx b/integrations/payments/agentcard.mdx
new file mode 100644
index 0000000..fcb9b8b
--- /dev/null
+++ b/integrations/payments/agentcard.mdx
@@ -0,0 +1,512 @@
+---
+title: "Agentcard"
+description: "Use Agentcard to approve browser checkouts against an enrolled payment method"
+---
+
+[agentcard](https://www.agentcard.sh/) collects a user's card in a hosted enrollment flow and authorizes each browser checkout against that enrolled payment method. the card number and cvc stay with agentcard. your agent receives non-secret aliases.
+
+## Before you start
+
+create a project-scoped client and vault. the examples below use these `kernel` and `vault` variables.
+
+
+
+```typescript TypeScript
+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 agentcardMode = process.env.AGENTCARD_MODE;
+if (agentcardMode !== "sandbox" && agentcardMode !== "live") {
+ throw new Error("set AGENTCARD_MODE to sandbox or live");
+}
+```
+
+```python Python
+import os
+
+from kernel import Kernel
+
+kernel = Kernel(project_id=os.environ["KERNEL_PROJECT_ID"])
+vault = kernel.vaults.upsert(name="user-12345")
+
+agentcard_mode = os.environ.get("AGENTCARD_MODE")
+if agentcard_mode not in {"sandbox", "live"}:
+ raise RuntimeError("set AGENTCARD_MODE to sandbox or live")
+```
+
+```bash CLI
+export AGENTCARD_MODE=live
+kernel vaults create --name user-12345
+```
+
+
+
+the vault api does not expose whether the configured agentcard credential is
+sandbox or live. `AGENTCARD_MODE` is an application-owned assertion, not a
+value read from KERNEL. set it from the deployment configuration that owns the
+agentcard credential, show the mode in internal checkout controls, and fail
+closed when it is missing or does not match the environment you intend to use.
+set `AGENTCARD_MODE=sandbox` instead only when that deployment uses a sandbox
+credential.
+
+## Lifecycle
+
+1. create a `wallet` item and open the returned `card_enrollment` action for the user.
+2. wait for the wallet to become `connected`.
+3. create a reusable `card` item with the merchant, amount, and currency.
+4. attach the vault to a browser and give `state.aliases` to the agent.
+5. when the browser submits a supported checkout, KERNEL holds the request and starts agentcard authorization.
+6. show the returned approval action to the user while the checkout remains in progress.
+7. agentcard executes the approved request, and KERNEL replays the processor response to the browser.
+
+the card item returns to `ready` after an authorization settles and can be used for another separately approved purchase. only one authorization can be pending on an item at a time.
+
+## Enroll a card
+
+before showing an agentcard enrollment option, list the vault's items. if an
+agentcard wallet already exists in any state, reuse it and do not let the user
+add another. show its existing action or status instead. the api makes item keys
+unique but does not currently enforce one wallet per provider, so the ui must
+enforce a maximum of one agentcard wallet per vault.
+
+the examples use `presentProviderAction`, an application-owned function that
+publishes the action to an authenticated session for the end user who owns the
+vault. bind the action to that user, vault, and item; apply a short application
+ttl capped by `wallet.expires_at` when present; and stop serving it when the
+action changes or disappears. derive `authenticatedUser` from the server-side
+session, not a request field. do not log the url or put it in model context. the
+[browser payment guide](/browsers/enable-payments-in-browser-agent#present-hosted-actions-in-your-application)
+defines the authenticated redirect and expiry contract for this helper.
+
+
+
+```typescript TypeScript
+const items = await kernel.vaults.items.list(vault.id);
+const agentcardWallets = items.filter(
+ (item) => item.type === "wallet" && item.spec.provider === "agentcard",
+);
+if (agentcardWallets.length > 1) {
+ throw new Error("vault has more than one agentcard wallet");
+}
+
+let wallet = agentcardWallets[0];
+if (!wallet) {
+ wallet = await kernel.vaults.items.upsert("agentcard-wallet", {
+ id_or_name: vault.id,
+ type: "wallet",
+ spec: { provider: "agentcard" },
+ });
+}
+
+if (wallet.action?.name === "card_enrollment") {
+ await presentProviderAction({
+ userID: authenticatedUser.id,
+ vaultID: vault.id,
+ item: wallet,
+ });
+}
+wallet = await kernel.vaults.items.retrieve(wallet.key, {
+ id_or_name: vault.id,
+ wait: 60,
+});
+```
+
+```python Python
+items = kernel.vaults.items.list(vault.id)
+agentcard_wallets = [
+ item
+ for item in items
+ if item.type == "wallet" and item.spec.provider == "agentcard"
+]
+if len(agentcard_wallets) > 1:
+ raise RuntimeError("vault has more than one agentcard wallet")
+
+wallet = agentcard_wallets[0] if agentcard_wallets else None
+if wallet is None:
+ wallet = kernel.vaults.items.upsert(
+ "agentcard-wallet",
+ id_or_name=vault.id,
+ type="wallet",
+ spec={"provider": "agentcard"},
+ )
+
+if wallet.action is not None and wallet.action.name == "card_enrollment":
+ present_provider_action(
+ user_id=authenticated_user.id,
+ vault_id=vault.id,
+ item=wallet,
+ )
+wallet = kernel.vaults.items.retrieve(
+ wallet.key,
+ id_or_name=vault.id,
+ wait=60,
+)
+```
+
+```bash CLI
+# create only when the list has no agentcard wallet
+kernel vaults items list user-12345 -o json
+kernel vaults wallets create user-12345 agentcard-wallet \
+ --provider agentcard \
+ --spec '{}' \
+ --open
+kernel vaults items get user-12345 agentcard-wallet --wait 60
+```
+
+
+
+
+ open the enrollment url in a trusted user-facing surface. don't give it to the
+ agent or open it in the agent-controlled checkout browser. run cli `--open`
+ only from a trusted, human-operated terminal because the command output can
+ contain the action url.
+
+
+`spec.user_id` can reuse a user who was already enrolled through another wallet in your organization. it cannot reference an arbitrary agentcard user.
+
+## Create a card item
+
+
+
+```typescript TypeScript
+const card = await kernel.vaults.items.upsert("notebook-order", {
+ id_or_name: vault.id,
+ type: "card",
+ spec: {
+ provider: "agentcard",
+ wallet: wallet.key,
+ merchant: "example shop",
+ amount: 2306,
+ currency: "usd",
+ },
+});
+
+if (card.state.status !== "ready" || !card.state.aliases) {
+ throw new Error(`card is ${card.state.status}`);
+}
+```
+
+```python Python
+card = kernel.vaults.items.upsert(
+ "notebook-order",
+ id_or_name=vault.id,
+ type="card",
+ spec={
+ "provider": "agentcard",
+ "wallet": wallet.key,
+ "merchant": "example shop",
+ "amount": 2306,
+ "currency": "usd",
+ },
+)
+
+if card.state.status != "ready" or card.state.aliases is None:
+ raise RuntimeError(f"card is {card.state.status}")
+```
+
+```bash CLI
+kernel vaults cards create user-12345 notebook-order \
+ --provider agentcard \
+ --spec '{
+ "wallet": "agentcard-wallet",
+ "merchant": "example shop",
+ "amount": 2306,
+ "currency": "usd"
+ }'
+kernel vaults items get user-12345 notebook-order -o json
+```
+
+
+
+`amount` uses minor currency units, so `2306` means 23.06 usd. omitting `card_id` lets the cardholder select an enrolled card on the approval screen. to pin a card, request the wallet's advertised `payment_methods` expansion and set a returned id as `spec.card_id`.
+
+### Reuse a card item for a new purchase
+
+`upsert` can retrieve an identical item, but it cannot replace the purchase
+specification at an existing key. for a later purchase, retrieve the reusable
+agentcard item and use `update` with the complete new specification:
+
+
+
+```typescript TypeScript
+let reusableCard = await kernel.vaults.items.retrieve("notebook-order", {
+ id_or_name: vault.id,
+ wait: 60,
+});
+
+if (
+ reusableCard.type !== "card" ||
+ reusableCard.spec.provider !== "agentcard" ||
+ (reusableCard.state.status !== "requested" &&
+ reusableCard.state.status !== "ready")
+) {
+ throw new Error(`card cannot be updated from ${reusableCard.state.status}`);
+}
+
+reusableCard = await kernel.vaults.items.update("notebook-order", {
+ id_or_name: vault.id,
+ spec: {
+ provider: "agentcard",
+ wallet: wallet.key,
+ merchant: "example books",
+ amount: 4199,
+ currency: "usd",
+ },
+});
+```
+
+```python Python
+reusable_card = kernel.vaults.items.retrieve(
+ "notebook-order",
+ id_or_name=vault.id,
+ wait=60,
+)
+
+if (
+ reusable_card.type != "card"
+ or reusable_card.spec.provider != "agentcard"
+ or reusable_card.state.status not in {"requested", "ready"}
+):
+ raise RuntimeError(
+ f"card cannot be updated from {reusable_card.state.status}"
+ )
+
+reusable_card = kernel.vaults.items.update(
+ "notebook-order",
+ id_or_name=vault.id,
+ spec={
+ "provider": "agentcard",
+ "wallet": wallet.key,
+ "merchant": "example books",
+ "amount": 4199,
+ "currency": "usd",
+ },
+)
+```
+
+```bash CLI
+kernel vaults items get user-12345 notebook-order --wait 60 -o json
+kernel vaults cards update user-12345 notebook-order \
+ --provider agentcard \
+ --spec '{
+ "wallet": "agentcard-wallet",
+ "merchant": "example books",
+ "amount": 4199,
+ "currency": "usd"
+ }'
+```
+
+
+
+the api accepts an agentcard card update only while the item is `requested` or
+`ready`. if it is `pending_approval`, finish and reconcile that authorization
+before preparing another purchase. if it is `degraded`, retrieve it to allow
+recovery and stop if it remains degraded. `update` replaces the full `spec`, so
+include `card_id` again when you want to keep the card pinned. never update an item
+to retry a failed, timed-out, or indeterminate checkout.
+
+agentcard has no per-item `test`, `merchant_url`, or domain allowlist. `merchant` is the name shown on the approval screen, not an enforced browsing origin. sandbox or live behavior comes from the agentcard credential configured for the integration and must match your application-owned `AGENTCARD_MODE` assertion before you use the aliases.
+
+## Complete the first checkout
+
+use this sequence for an agentcard checkout:
+
+1. require exactly one agentcard wallet in the vault and wait for it to become `connected`.
+2. create a headful browser with the vault attached, surface `browser_live_view_url` through your trusted application, and navigate to the checkout. keep this same browser for verification and submission so location-dependent pricing cannot change between the confirmed purchase and the outgoing request.
+3. independently verify the merchant, items, active presentment amount, and active presentment currency. for a stripe payment link, prefer `account_settings.display_name`, `line_item_group.total`, `line_item_group.currency`, and `line_item_group.line_items` from the structured payment-link response when you don't have an order backend. use dom text and test ids only as supplemental checks because stripe can duplicate or omit them across layouts.
+4. collect merchant-required fields such as email, billing name, and postal code from the end user. identify checkout-specific agent disclosures and instruct the agent to answer them truthfully in the normal form.
+5. show the verified purchase to the end user. after confirmation, create or update the card item from that same frozen object and require it to be `ready`. the vault attachment covers items created later in the same vault.
+6. start the card and event observer before checkout submission. keep it running concurrently while the browser request is held.
+7. give the browser agent the aliases, separately collected customer fields, and any required disclosure answer. submit the merchant form once.
+8. publish the approval action through the authenticated, expiring application flow. never send it to the checkout browser or agent.
+9. after the authorization settles, reconcile authorization state, item events, the checkout page, and the merchant order record. don't prepare the next purchase until this attempt is terminal or explicitly classified as indeterminate.
+
+### Surface the live view
+
+
+
+```typescript TypeScript
+const browser = await kernel.browsers.create({
+ vaults: [{ id: vault.id }],
+ headless: false,
+ timeout_seconds: 1800,
+});
+
+if (!browser.browser_live_view_url) {
+ throw new Error("headful browser did not return a live view url");
+}
+await presentLiveView({
+ userID: authenticatedUser.id,
+ sessionID: browser.session_id,
+ url: browser.browser_live_view_url,
+});
+```
+
+```python Python
+browser = kernel.browsers.create(
+ vaults=[{"id": vault.id}],
+ headless=False,
+ timeout_seconds=1800,
+)
+
+if browser.browser_live_view_url is None:
+ raise RuntimeError("headful browser did not return a live view url")
+present_live_view(
+ user_id=authenticated_user.id,
+ session_id=browser.session_id,
+ url=browser.browser_live_view_url,
+)
+```
+
+```bash CLI
+kernel browsers create --vault user-12345 -o json
+```
+
+
+
+`presentLiveView` represents an application-owned route. store the url
+server-side with the authenticated end user and browser session binding, and
+render or embed it only after checking that session. retain the same binding
+through confirmation and approval pauses. remove it when the browser is deleted
+or times out, and don't put it in logs or model context. see
+[live view](/browsers/live-view#embedding-in-an-iframe) for iframe and csp
+requirements.
+
+### Run checkout and observation concurrently
+
+start the observer before calling the browser agent. the application-owned
+functions below represent the observer, your existing agent loop, your order
+backend, and your reconciliation policy:
+
+
+
+```typescript TypeScript
+const stop = new AbortController();
+const observer = observePayment(stop.signal);
+
+try {
+ const pageOutcome = await runBrowserAgentCheckout({
+ browser,
+ aliases: card.state.aliases,
+ verifiedPurchase,
+ customerFields,
+ disclosure: "I am an AI agent acting on behalf of someone else",
+ });
+ const merchantOrder = await waitForMerchantResolution(
+ verifiedPurchase.orderID,
+ );
+ const current = await kernel.vaults.items.retrieve(card.key, {
+ id_or_name: vault.id,
+ });
+ if (current.type !== "card" || current.state.provider !== "agentcard") {
+ throw new Error("expected an agentcard card item");
+ }
+ const events = await kernel.vaults.items.events(card.key, {
+ id_or_name: vault.id,
+ });
+
+ const result = reconcileAgentcard({
+ verifiedPurchase,
+ merchantOrder,
+ authorization: current.state.authorization,
+ events,
+ pageOutcome,
+ });
+ handleReconciledResult(result);
+} finally {
+ stop.abort();
+ try {
+ await observer;
+ } finally {
+ await kernel.browsers.deleteByID(browser.session_id);
+ }
+}
+```
+
+```python Python
+from threading import Event, Thread
+
+stop = Event()
+observer = Thread(target=observe_payment, args=(stop,))
+observer.start()
+
+try:
+ page_outcome = run_browser_agent_checkout(
+ browser=browser,
+ aliases=card.state.aliases,
+ verified_purchase=verified_purchase,
+ customer_fields=customer_fields,
+ disclosure="I am an AI agent acting on behalf of someone else",
+ )
+ merchant_order = wait_for_merchant_resolution(verified_purchase.order_id)
+ current = kernel.vaults.items.retrieve(card.key, id_or_name=vault.id)
+ if current.type != "card" or current.state.provider != "agentcard":
+ raise RuntimeError("expected an agentcard card item")
+ events = kernel.vaults.items.events(card.key, id_or_name=vault.id)
+
+ result = reconcile_agentcard(
+ verified_purchase=verified_purchase,
+ merchant_order=merchant_order,
+ authorization=current.state.authorization,
+ events=events,
+ page_outcome=page_outcome,
+ )
+ handle_reconciled_result(result)
+finally:
+ stop.set()
+ try:
+ observer.join()
+ finally:
+ kernel.browsers.delete_by_id(browser.session_id)
+```
+
+
+
+the observer must have its own terminal-state loop and application deadline.
+cancel it after the merchant reaches a terminal state or the deadline expires.
+if the deadline expires, retain the vault id, card key, browser id, and last
+event id, classify the attempt as indeterminate, and don't resubmit checkout.
+
+## Handle checkout approval
+
+agentcard does not advertise the `authorize` operation. authorization begins only after an attached browser submits a supported checkout request containing the aliases.
+
+while the request is held, retrieve the card and send `action.url` through the
+same authenticated, expiring user-action flow used for enrollment. stop serving
+the url when it disappears, changes, expires, or the authorization settles.
+`state.authorization` describes the pending or most recent authorization,
+including its `status`, `browser_id`, expected and actual amounts when
+available, charge result, and replay result.
+
+```bash CLI
+kernel vaults items get user-12345 notebook-order --wait 60 --open
+kernel vaults items events user-12345 notebook-order --wait 60 -o json
+```
+
+each `--wait` performs one bounded observation. repeat the relevant command to
+continue observing an existing checkout; don't use it as evidence that a
+payment succeeded or failed. run `--open` only in a trusted, human-operated
+terminal and never pass its output to an agent.
+
+| field | interpretation |
+| ---------------------------------- | ---------------------------------------------------------------------------------------------------- |
+| `amount_authority: 'display_only'` | the request does not prove a charge amount; tokenization requests can have this value |
+| `amount_verified` | whether the observed amount matched the approved amount when verification was available |
+| `charged_kind` | `captured`, `authorized`, or `none` as reported by the provider path |
+| `replay_attempted` | whether KERNEL attempted to replay the processor response |
+| `replay_delivered` | whether that response reached the browser; this does not confirm a merchant order |
+
+declines, expirations, and provider failures can return a stripe-shaped error to the browser. the reusable item can still return to `ready`, so item status alone does not prove that the purchase succeeded or failed.
+
+your reconciliation policy must return `succeeded` only when the merchant order
+record confirms a paid order whose merchant, items, amount, and currency match
+the frozen purchase object. treat `state.authorization`, `charged_kind`,
+`replay_delivered`, item events, and the checkout page as supporting evidence.
+merchant success text is page-specific: **Thanks for your payment** can appear
+for one stripe checkout, but no generic success-text matcher proves that the
+merchant created the expected order. return `indeterminate` when the sources
+disagree or the merchant record is unavailable, and don't retry automatically.
+
+pass the aliases to the [browser agent payments guide](/browsers/enable-payments-in-browser-agent) and observe item events while the checkout runs.
diff --git a/integrations/payments/overview.mdx b/integrations/payments/overview.mdx
new file mode 100644
index 0000000..4fda21e
--- /dev/null
+++ b/integrations/payments/overview.mdx
@@ -0,0 +1,115 @@
+---
+title: "overview"
+description: "Add payments to a browser agent without exposing card data to your application, agent, or browser"
+---
+
+your browser agent can complete a web checkout without bringing your application, agent, or browser into pci dss scope. a provider-hosted flow collects and stores the user's payment method, so neither you nor your agent handles the card number or cvc.
+
+KERNEL connects that payment method
+to a [vault](/vaults), returns non-secret aliases, and resolves those aliases at
+browser egress. the agent fills the checkout form with the aliases. the merchant
+page creates its normal payment request.
+KERNEL handles authorization and
+payment handoff outside the browser.
+
+## How payments work
+
+both supported providers use the same integration shape:
+
+1. create a vault for the user or task.
+2. create a wallet item and send the user through the provider-hosted collection flow.
+3. create a card item for the intended purchase and wait for aliases.
+4. attach the vault when you create the browser session.
+5. give the aliases to the agent and let it complete the merchant's checkout.
+6. complete any provider-hosted approval and inspect item events alongside the merchant's order state.
+
+each vault must contain at most one wallet item for each provider. before
+showing a provider connection option, list the vault's items. if that provider
+already has a wallet in any state, hide the add option and reuse or recover the
+existing item. when both stripe link and agentcard wallets exist, show both as
+configured and do not offer either provider again.
+
+```mermaid
+flowchart LR
+ U[user] --> H[provider-hosted collection]
+ H --> W[wallet item]
+ W --> C[card item and aliases]
+ C --> A[agent fills checkout]
+ A --> E[KERNEL egress]
+ E --> P[payment provider]
+ P --> M[merchant response]
+```
+
+the card number and cvc stay outside the agent-controlled environment. the browser sees format-valid aliases and the processor-shaped response, not the underlying payment credential.
+
+## Choose a provider
+
+
+
+ collect a stripe link wallet and approve a one-use credential for a specific
+ purchase.
+
+
+ enroll a card with Agentcard and approve each checkout while the request is
+ held.
+
+
+
+
+ KERNEL is actively adding native
+ integrations with more payment providers. email
+ [support@kernel.sh](mailto:support@kernel.sh) to request one.
+
+
+| behavior | stripe link | agentcard |
+| ------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------- |
+| payment-method collection | hosted `link_oauth` action | hosted `card_enrollment` action |
+| purchase authorization | explicit `authorize` operation before checkout | approval begins when the browser submits checkout |
+| payment handoff | one-use credential substituted at egress | provider executes the held request and returns the response |
+| reuse | card item and aliases are consumed on first substitution | card item and aliases remain reusable; each checkout needs approval |
+| environment | live only | configured agentcard credential; not exposed through the vault api |
+
+choose [stripe link](/integrations/payments/stripe-link) when each purchase needs a newly approved, one-use credential. choose [agentcard](/integrations/payments/agentcard) when the same enrolled card must support multiple separately approved purchases.
+
+## Supported checkouts
+
+the initial release recognizes form-encoded card requests sent to `api.stripe.com` on these paths:
+
+- `/v1/payment_methods`
+- `/v1/tokens`
+- `/v1/payment_intents/{id}/confirm`
+- `/v1/payment_pages/{id}/confirm`
+
+the request must contain the complete alias set. arbitrary processors, custom request formats, and every possible stripe integration are not supported in the initial release.
+
+
+ don't retry a failed, timed-out, rejected, or indeterminate payment. a browser
+ error, missing response, consumed link item, or reusable agentcard item does
+ not prove whether the merchant created an order or money moved. inspect the
+ existing item events and the merchant's order state before taking another
+ action.
+
+
+## Next step
+
+configure [stripe link](/integrations/payments/stripe-link) or [agentcard](/integrations/payments/agentcard), then follow [Enable Payments in a Browser Agent](/browsers/enable-payments-in-browser-agent) to attach the vault and give payment aliases to your agent.
+
+the provider pages show the CLI commands for creating wallets and cards. once
+the card item is ready, the shared CLI flow is:
+
+```bash CLI
+kernel vaults create --name user-12345
+kernel vaults items get user-12345 notebook-order --wait 60 -o json
+kernel browsers create --vault user-12345 -o json
+```
+
+`--wait` performs one bounded observation. it does not confirm that a payment
+succeeded, and the CLI does not submit or retry merchant payments.
diff --git a/integrations/payments/stripe-link.mdx b/integrations/payments/stripe-link.mdx
new file mode 100644
index 0000000..c5c8905
--- /dev/null
+++ b/integrations/payments/stripe-link.mdx
@@ -0,0 +1,334 @@
+---
+title: "stripe link"
+description: "Use stripe link to approve a one-use payment credential for a browser checkout"
+---
+
+[stripe link](https://stripe.com/payments/link) connects a user's wallet through oauth and issues a one-use payment credential for an approved purchase. KERNEL stores that credential encrypted, gives your agent non-secret aliases, substitutes the credential at browser egress, and then consumes the card item.
+
+## Before you start
+
+create a project-scoped client and vault. the examples below use these `kernel` and `vault` variables.
+
+
+
+```typescript TypeScript
+import Kernel from "@onkernel/sdk";
+
+const kernel = new Kernel({ projectID: process.env.KERNEL_PROJECT_ID! });
+const vault = await kernel.vaults.upsert({ name: "user-12345" });
+```
+
+```python Python
+import os
+
+from kernel import Kernel
+
+kernel = Kernel(project_id=os.environ["KERNEL_PROJECT_ID"])
+vault = kernel.vaults.upsert(name="user-12345")
+```
+
+```bash CLI
+kernel vaults create --name user-12345
+```
+
+
+
+## Lifecycle
+
+1. create a `wallet` item with the link oauth specification.
+2. open the returned `link_oauth` action for the user and wait for the wallet to become `connected`.
+3. request the advertised `payment_methods` expansion and let the user choose an eligible method.
+4. create a `card` item with the purchase details.
+5. retrieve the card, verify that it advertises `authorize`, and perform that operation after explicit user approval.
+6. complete the returned `spend_approval` or `push_approval` action and wait for `state.status` to become `ready`.
+7. use `state.aliases` in an attached browser. the first supported substitution changes the item to `consumed`.
+
+## Connect a wallet
+
+before showing a stripe link connection option, list the vault's items. if a
+link wallet already exists in any state, reuse it and do not let the user add
+another. show its existing action or status instead. the api makes item keys
+unique but does not currently enforce one wallet per provider, so the ui must
+enforce a maximum of one link wallet per vault.
+
+the examples use `presentProviderAction`, an application-owned function that
+publishes the action to an authenticated session for the end user who owns the
+vault. bind the action to that user, vault, and item; apply a short application
+ttl capped by `wallet.expires_at` when present; and stop serving it when the
+action changes or disappears. derive `authenticatedUser` from the server-side
+session, not a request field. do not log the url or put it in model context.
+
+
+
+```typescript TypeScript
+const items = await kernel.vaults.items.list(vault.id);
+const linkWallets = items.filter(
+ (item) => item.type === "wallet" && item.spec.provider === "link",
+);
+if (linkWallets.length > 1) {
+ throw new Error("vault has more than one link wallet");
+}
+
+let wallet = linkWallets[0];
+if (!wallet) {
+ wallet = await kernel.vaults.items.upsert("link-wallet", {
+ id_or_name: vault.id,
+ type: "wallet",
+ spec: {
+ provider: "link",
+ authorization: {
+ method: "oauth",
+ client: { type: "kernel_managed" },
+ },
+ },
+ });
+}
+
+if (wallet.action?.name === "link_oauth") {
+ await presentProviderAction({
+ userID: authenticatedUser.id,
+ vaultID: vault.id,
+ item: wallet,
+ });
+}
+wallet = await kernel.vaults.items.retrieve(wallet.key, {
+ id_or_name: vault.id,
+ wait: 60,
+});
+```
+
+```python Python
+items = kernel.vaults.items.list(vault.id)
+link_wallets = [
+ item
+ for item in items
+ if item.type == "wallet" and item.spec.provider == "link"
+]
+if len(link_wallets) > 1:
+ raise RuntimeError("vault has more than one link wallet")
+
+wallet = link_wallets[0] if link_wallets else None
+if wallet is None:
+ wallet = kernel.vaults.items.upsert(
+ "link-wallet",
+ id_or_name=vault.id,
+ type="wallet",
+ spec={
+ "provider": "link",
+ "authorization": {
+ "method": "oauth",
+ "client": {"type": "kernel_managed"},
+ },
+ },
+ )
+
+if wallet.action is not None and wallet.action.name == "link_oauth":
+ present_provider_action(
+ user_id=authenticated_user.id,
+ vault_id=vault.id,
+ item=wallet,
+ )
+wallet = kernel.vaults.items.retrieve(
+ wallet.key,
+ id_or_name=vault.id,
+ wait=60,
+)
+```
+
+```bash CLI
+# create only when the list has no link wallet
+kernel vaults items list user-12345 -o json
+kernel vaults wallets create user-12345 link-wallet \
+ --provider link \
+ --spec '{"authorization":{"method":"oauth","client":{"type":"kernel_managed"}}}' \
+ --open
+kernel vaults items get user-12345 link-wallet --wait 60
+```
+
+
+
+
+ open collection and approval urls in a trusted user-facing surface. don't give
+ them to the agent or print full wallet responses into model context. run cli
+ `--open` only from a trusted, human-operated terminal because the command
+ output can contain the action url.
+
+
+## Select a payment method
+
+request `payment_methods` only when `available_expansions` advertises it. the expansion comes from link at request time and is not persisted in the vault item.
+
+
+
+```typescript TypeScript
+if (
+ !wallet.available_expansions.some(({ type }) => type === "payment_methods")
+) {
+ throw new Error("payment methods are unavailable");
+}
+
+wallet = await kernel.vaults.items.retrieve(wallet.key, {
+ id_or_name: vault.id,
+ expand: ["payment_methods"],
+});
+
+const methods = wallet.expanded?.payment_methods ?? [];
+for (const method of methods) {
+ console.log(method.id, method.display, method.capabilities);
+}
+
+const paymentMethod = methods.find(
+ ({ id }) => id === process.env.LINK_PAYMENT_METHOD_ID,
+);
+if (
+ !paymentMethod ||
+ paymentMethod.capabilities.single_use_card?.eligible === false
+) {
+ throw new Error("select an available payment method");
+}
+```
+
+```python Python
+if not any(expansion.type == "payment_methods" for expansion in wallet.available_expansions):
+ raise RuntimeError("payment methods are unavailable")
+
+wallet = kernel.vaults.items.retrieve(
+ wallet.key,
+ id_or_name=vault.id,
+ expand=["payment_methods"],
+)
+
+methods = (wallet.expanded.payment_methods or []) if wallet.expanded else []
+for method in methods:
+ print(method.id, method.display, method.capabilities)
+
+payment_method = next(
+ (
+ method
+ for method in methods
+ if method.id == os.environ["LINK_PAYMENT_METHOD_ID"]
+ ),
+ None,
+)
+if payment_method is None or (
+ payment_method.capabilities.single_use_card is not None
+ and payment_method.capabilities.single_use_card.eligible is False
+):
+ raise RuntimeError("select an available payment method")
+```
+
+```bash CLI
+kernel vaults wallets payment-methods user-12345 link-wallet -o json
+```
+
+
+
+show the returned methods in a trusted user-facing surface, let the user choose one, and set its id as `LINK_PAYMENT_METHOD_ID`. missing capability metadata means eligibility is unknown. only `eligible: false` is an explicit negative result.
+
+## Create and authorize a card item
+
+
+
+```typescript TypeScript
+let card = await kernel.vaults.items.upsert("notebook-order", {
+ id_or_name: vault.id,
+ type: "card",
+ spec: {
+ provider: "link",
+ wallet: wallet.key,
+ payment_method_id: paymentMethod.id,
+ amount: 2306,
+ currency: "usd",
+ merchant_name: "example shop",
+ merchant_url: "https://shop.example.com",
+ context:
+ "buy one notebook from example shop for a total of 23.06 usd, including tax " +
+ "and shipping. this request is for this purchase only and must not be repeated.",
+ },
+});
+
+card = await kernel.vaults.items.retrieve(card.key, { id_or_name: vault.id });
+if (!card.available_operations.some(({ type }) => type === "authorize")) {
+ throw new Error("authorization is unavailable");
+}
+
+card = await kernel.vaults.items.performOperation(card.key, {
+ id_or_name: vault.id,
+ type: "authorize",
+});
+if (card.action && "url" in card.action) {
+ await presentProviderAction({
+ userID: authenticatedUser.id,
+ vaultID: vault.id,
+ item: card,
+ });
+}
+```
+
+```python Python
+card = kernel.vaults.items.upsert(
+ "notebook-order",
+ id_or_name=vault.id,
+ type="card",
+ spec={
+ "provider": "link",
+ "wallet": wallet.key,
+ "payment_method_id": payment_method.id,
+ "amount": 2306,
+ "currency": "usd",
+ "merchant_name": "example shop",
+ "merchant_url": "https://shop.example.com",
+ "context": (
+ "buy one notebook from example shop for a total of 23.06 usd, including tax "
+ "and shipping. this request is for this purchase only and must not be repeated."
+ ),
+ },
+)
+
+card = kernel.vaults.items.retrieve(card.key, id_or_name=vault.id)
+if not any(operation.type == "authorize" for operation in card.available_operations):
+ raise RuntimeError("authorization is unavailable")
+
+card = kernel.vaults.items.perform_operation(
+ card.key,
+ id_or_name=vault.id,
+ type="authorize",
+)
+if card.action is not None and hasattr(card.action, "url"):
+ present_provider_action(
+ user_id=authenticated_user.id,
+ vault_id=vault.id,
+ item=card,
+ )
+```
+
+```bash CLI
+kernel vaults cards create user-12345 notebook-order \
+ --provider link \
+ --spec '{
+ "wallet": "link-wallet",
+ "payment_method_id": "pm_123",
+ "amount": 2306,
+ "currency": "usd",
+ "merchant_name": "example shop",
+ "merchant_url": "https://shop.example.com",
+ "context": "buy one notebook from example shop for a total of 23.06 usd, including tax and shipping. this request is for this purchase only and must not be repeated."
+ }'
+kernel vaults items get user-12345 notebook-order -o json
+kernel vaults items invoke user-12345 notebook-order authorize --open
+kernel vaults items get user-12345 notebook-order --wait 60 -o json
+```
+
+
+
+`amount` uses minor currency units, so `2306` means 23.06 usd. link accepts values from 1 to 500000. `context` must contain at least 100 characters. card creation is live-only, and `spec.test` is not supported.
+
+`merchant_url` supplies provider context. KERNEL derives its registrable domain into `state.domains` when authorization starts, but `state.domains` is metadata rather than an enforced browser-origin allowlist.
+
+## Use the aliases
+
+after the user completes the approval action, retrieve the card with `wait: 60` until it becomes `ready`. pass `state.aliases` to the [browser agent payments guide](/browsers/enable-payments-in-browser-agent).
+
+the first supported checkout request that contains the aliases consumes the item and clears its encrypted card value. `consumed` means the credential was substituted, not that the processor accepted the payment or the merchant created an order.
+
+don't repeat `authorize` or create a replacement item to retry an unknown purchase. inspect item events and the merchant's order state first.
diff --git a/style.css b/style.css
index 2b7c61b..8c7fcc8 100644
--- a/style.css
+++ b/style.css
@@ -3,6 +3,10 @@
text-transform: lowercase !important;
}
+.kernel-brand-name {
+ text-transform: uppercase !important;
+}
+
/* preserve code formatting - exclude code blocks and inline code */
code,
pre,
diff --git a/vaults.mdx b/vaults.mdx
new file mode 100644
index 0000000..7eca52c
--- /dev/null
+++ b/vaults.mdx
@@ -0,0 +1,233 @@
+---
+title: "Vaults"
+description: "Let browser agents use sensitive values without exposing them to the agent or browser"
+---
+
+**status:** preview
+
+browser agents become more useful when they can finish tasks that require
+sensitive values: checking out with a card, signing in with a password, or
+completing a form with identity data. handing those values to an agent exposes
+them to model context, logs, page scripts, extensions, devtools, screenshots,
+and browser replay. withholding them keeps the agent from finishing the task.
+
+KERNEL vaults let an attached browser
+session use a sensitive value without revealing that value to the agent or the
+browser vm. the agent receives a non-sensitive stand-in called an alias. the
+browser submits the alias, and KERNEL
+resolves it at egress after the request leaves the browser vm.
+
+vaults are a general primitive for separating what an agent can use from what it
+can see. typed items describe provider-backed values, aliases give agents usable
+stand-ins, and immutable browser bindings control where those aliases can be
+resolved.
+
+
+ vaults are in preview. the initial release supports `wallet` and `card` items
+ for [stripe link](/integrations/payments/stripe-link) and
+ [agentcard](/integrations/payments/agentcard). the vault model is not limited
+ to payments, but no other item types or providers are supported in this
+ release.
+
+
+## How vaults work
+
+### Values do not come back through the api
+
+sensitive values do not have a read path through the vault api. item responses
+return non-sensitive specifications, state, masks, aliases, actions, and events,
+but not the underlying value. for the initial payment integrations, a
+provider-hosted flow collects the user's payment method and the provider-backed
+card reaches the vault without passing through your application or agent.
+
+### Agents use aliases
+
+each item can publish non-sensitive, format-valid aliases. in the initial
+release, a card item can return a luhn-valid 16-digit number, a three-digit cvc,
+and an expiry month and year. these values pass client-side checkout validation
+but cannot resolve unless the browser session and vault are bound together.
+
+### Vaults attach to browser sessions
+
+attach one or more vaults when you create a browser. the binding cannot change
+for the life of the session and is enforced outside the browser vm. the agent
+uses aliases like any other form input.
+
+### Substitution happens at egress
+
+the KERNEL egress layer runs outside
+the browser vm. when it recognizes a request containing an alias, it verifies
+the browser, session, project, vault, item, and provider state before resolving
+the provider-backed value. the browser receives the destination's response
+without receiving that value. resolution fails closed when any binding or state
+check does not match.
+
+## Resource model
+
+| resource | technical behavior |
+| ------------------ | --------------------------------------------------------------------------------------------------- |
+| vault | resource with an immutable `name` that groups provider-backed items |
+| item | typed, provider-backed value addressed by an immutable `key`; initial types are `wallet` and `card` |
+| alias | non-sensitive, format-valid stand-in returned in item state; aliases belong to an item |
+| action | user interaction returned as `action`, such as a hosted collection or approval url |
+| operation | explicit api action advertised in `available_operations`; the initial operation type is `authorize` |
+| expansion | live provider data advertised in `available_expansions`; the initial expansion is `payment_methods` |
+| event | immutable item observation with `id`, `name`, optional `browser_id`, `data`, and `created_at` |
+| browser attachment | vault reference fixed when the browser is created |
+
+```mermaid
+flowchart LR
+ T[trusted collection surface] --> I[vault item]
+ I --> A[non-sensitive alias]
+ A --> B[attached browser session]
+ I --> E[egress resolution]
+ B --> E
+ A --> E
+ E --> D[destination]
+```
+
+## Scope and attachment
+
+select project scope on the sdk client or use a project-scoped api key. for direct api requests, `X-Kernel-Project` accepts a project id or name. `project_id` is not accepted in a vault request body. without explicit project scope, KERNEL uses the organization's default project.
+
+attach vaults when you create a browser:
+
+
+
+```typescript TypeScript
+const browser = await kernel.browsers.create({
+ vaults: [{ id: vault.id }],
+});
+```
+
+```python Python
+browser = kernel.browsers.create(
+ vaults=[{"id": vault.id}],
+)
+```
+
+```bash CLI
+kernel browsers create --vault user-12345 -o json
+```
+
+
+
+the `vaults` array supports up to 20 references. each reference accepts exactly one of `id` or `name`, and attachments cannot change after browser creation. a browser and vault must belong to the same project.
+
+attachment grants the browser access to the vault, not to a selected set of items. items created later in the same vault are available to every attached browser in that project. use separate vaults when browser tasks must not share access. deleting a vault or item invalidates its provider-backed values and aliases.
+
+## Api behavior
+
+create or retrieve a vault by its immutable `name`. names accept 1–255 letters, numbers, `.`, `_`, and `-`, but can't use a cuid-like value that could be mistaken for a vault id. vault responses contain `id`, `name`, `created_at`, and `updated_at`.
+
+```bash CLI
+kernel vaults create --name user-12345
+kernel vaults get user-12345 -o json
+kernel vaults items list user-12345 -o json
+```
+
+item keys are immutable and accept 1–255 letters, numbers, `.`, `_`, and `-`. creating an item at an existing key succeeds only when its type, provider, and specification match the existing item and its lifecycle permits retrieval. otherwise, the api returns a conflict.
+
+retrieve an item before acting on it. responses expose these fields and advertise what the current state permits:
+
+| field | behavior |
+| ---------------------------------------- | ---------------------------------------------------- |
+| `id`, `key`, `type` | stable item identity; `key` and `type` cannot change |
+| `spec` | provider-specific input saved with the item |
+| `state` | provider-specific status and non-secret output |
+| `action` | current user action, when one is required |
+| `available_operations` | operations valid in the current state |
+| `available_expansions` | live provider data the item can request |
+| `expanded` | requested live data; not persisted on the item |
+| `expires_at`, `created_at`, `updated_at` | item timestamps when present |
+
+when `action` is present, complete it in a trusted user-facing surface. invoke only operations listed in `available_operations`, and request only expansions listed in `available_expansions`. don't hard-code provider transitions from a previous response.
+
+item reads accept `wait` values from 0–60 seconds. a read returns early when the item no longer has an unresolved authorization or approval transition. event reads support the same maximum wait and return an ordered array. use the last event `id` as the `after` cursor for newer events.
+
+deleting a vault invalidates every item and alias it contains.
+
+see the [vaults api reference](https://kernel.sh/docs/api-reference/vaults/create-or-retrieve-a-vault-by-immutable-name) for endpoints and complete request and response schemas.
+
+## Payments first
+
+the initial release applies the vault primitive to browser checkout. a wallet
+connects an end user's payment method through a provider-hosted flow. a card item
+then publishes aliases that an attached browser can enter into a supported
+checkout. authorization and payment handoff happen outside the browser vm.
+
+stripe link creates a one-use card for an approved purchase. agentcard keeps a
+reusable card item and requests approval for each checkout. wallet connection,
+authorization, provider handoff, and checkout observations are recorded as
+immutable events without card data.
+
+read the [payments overview](/integrations/payments/overview) for the shared
+lifecycle or use the [stripe link](/integrations/payments/stripe-link) and
+[agentcard](/integrations/payments/agentcard) provider guides.
+
+## Initial item specifications
+
+the initial release accepts these `spec` fields. fields not listed here are rejected.
+
+### Wallets
+
+| provider | required fields | optional fields |
+| --------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
+| link | `provider: 'link'`, `authorization.method: 'oauth'`, `authorization.client.type: 'kernel_managed'` | none |
+| agentcard | `provider: 'agentcard'` | `user_id` for a user already enrolled through a wallet in the organization |
+
+for payment settings ui, enforce at most one wallet per provider in each vault.
+the api currently enforces uniqueness by item key, not by wallet provider, so a
+different key can create a second wallet for the same provider. list items before
+rendering provider options, hide the add option whenever that provider already
+has a wallet in any state, and reuse or recover the existing item.
+
+### Cards
+
+| provider | required fields | optional fields |
+| --------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
+| link | `provider`, `wallet`, `payment_method_id`, `amount`, `currency`, `merchant_name`, `merchant_url`, `context` | `line_items`, `totals`, `metadata`, `expires_at` |
+| agentcard | `provider`, `wallet`, `merchant`, `amount`, `currency` | `card_id` |
+
+a card's `spec.wallet` must reference a wallet in the same vault and from the
+same provider.
+
+`amount` uses minor currency units. link accepts 1–500000, requires a three-letter `currency`, limits `merchant_name` to 255 characters, requires an absolute http or https `merchant_url`, and requires at least 100 characters in `context`. its optional `expires_at` is a unix timestamp in seconds.
+
+agentcard accepts amounts from 1–9007199254740991 and a three-letter `currency`. `merchant` accepts 1–120 printable characters without control characters. `card_id` uses the provider's `vc_` identifier, and wallet `user_id` uses its `usr_` identifier.
+
+link `line_items` support `name`, `quantity`, `unit_amount`, `description`, `sku`, `url`, `image_url`, `product_url`, and `totals`. each `totals` entry supports `type`, `display_text`, and `amount`. link `metadata` accepts string values.
+
+card updates replace the complete `spec`; they are not partial merges. link card items can update only while `requested`. agentcard card items can update while `requested` or `ready`, but not while approval is pending.
+
+deleting a card consumes its aliases and clears any stored provider value.
+deleting a wallet also invalidates its dependent cards.
+
+## Initial payment actions, states, and aliases
+
+`action.name` can be `link_oauth`, `spend_approval`, `push_approval`, `collect`, `mfa`, `embedded_ceremony`, or `card_enrollment`. actions that require a hosted interaction include a `url`. don't send action urls or provider authorization material to the agent.
+
+wallet status values are:
+
+- link: `pending_authorization`, `connected`, `declined`, `reconnect_required`, `degraded`
+- agentcard: `pending_authorization`, `connected`, `degraded`
+
+card status values are:
+
+- link: `requested`, `pending_authorization`, `ready`, `consumed`, `expired`, `declined`
+- agentcard: `requested`, `ready`, `pending_approval`, `degraded`
+
+card state can include `masks.brand`, `masks.last4`, and read-only aliases: `number`, `cvc`, `exp_month`, and `exp_year`. aliases are non-sensitive stand-ins, not standalone credentials or permission to use the provider-backed value.
+
+## What's next
+
+the data model is deliberately generic: a vault holds typed items, each item has
+a provider-specific specification and state, and each item can publish aliases.
+payments are the first application of that model.
+
+we plan to extend the same primitive to credentials, identity documents, and
+other sensitive fields. those item types could let an agent sign in without
+seeing the password or complete an application without seeing a government
+identifier. they are not available in the preview release.
+
+to complete a checkout with the item types available today, follow [Enable Payments in a Browser Agent](/browsers/enable-payments-in-browser-agent).