diff --git a/auth/configuration.mdx b/auth/configuration.mdx
index dd5aaf8b..d84fba9c 100644
--- a/auth/configuration.mdx
+++ b/auth/configuration.mdx
@@ -685,7 +685,7 @@ Pin the auth flow to a specific [proxy](/proxies/overview) so logins, health che
How stable the exit IP is depends on the proxy type:
-- **[ISP](/proxies/isp)** proxies provide a static exit IP that persists across sessions, so the initial login, health checks, and reauths all exit through the same IP. The IP only changes in rare ISP-initiated replacement events or a temporary [failover to a backup endpoint](/proxies/isp#backup-endpoint-failover).
+- **[ISP](/proxies/isp)** proxies provide a static exit IP that persists across sessions, so the initial login, health checks, and reauths all exit through the same IP. The primary IP stays fixed unless you move a proxy backed by a reserved ISP allocation or an ISP initiates a replacement; traffic can temporarily use a different IP during a [failover to a backup endpoint](/proxies/isp#backup-endpoint-failover).
- **[Residential](/proxies/residential)** proxies rotate IPs per connection — use them when you need legitimacy from a real ISP pool but can tolerate IP changes.
- **[Custom (BYO)](/proxies/custom)** proxies route through whatever you point them at, so pick one if the static IP must be infrastructure you control (e.g. an allowlisted egress your security team owns).
diff --git a/browsers/viewport.mdx b/browsers/viewport.mdx
index 0c8ecd22..f6f24ed0 100644
--- a/browsers/viewport.mdx
+++ b/browsers/viewport.mdx
@@ -198,7 +198,7 @@ Kernel supports specific viewport configurations tuned for optimal performance a
| Mobile | 390 | 844 | 60 Hz |
When specifying a viewport:
-- **Width** and **Height** are required
+- **Width** and **Height** are required. Headful browsers require each dimension to be at least 64 pixels; headless browsers accept any positive integer.
- **Viewport configurations** outside of the supported list are available, but they are provided on a "use at your own risk" basis and may not work as expected.
- **Refresh Rate** is optional - if omitted, it will be automatically determined from the width and height combination
diff --git a/info/api-key-permissions.mdx b/info/api-key-permissions.mdx
index 6d27711c..8d9b61e5 100644
--- a/info/api-key-permissions.mdx
+++ b/info/api-key-permissions.mdx
@@ -43,7 +43,7 @@ Resources must be inside the new key's organization, or its project for a projec
| `vaults:create` | Create vaults. Grant it on a project or organization. |
| `vaults:read` | List vaults and view vault and item metadata. |
| `vaults:use` | Link vaults to browsers, fill credentials, and run vault item operations. |
-| `vaults:write` | Create, update, and delete vault items. |
+| `vaults:write` | Create, update, and delete vault items, including replacing a customer-refreshed wallet's stored access token. |
| `vaults:delete` | Delete vaults and their items. |
### Proxies
@@ -51,9 +51,9 @@ Resources must be inside the new key's organization, or its project for a projec
| Action | Allows |
| --- | --- |
| `proxies:create` | Create proxies. Grant it on a project or organization. |
-| `proxies:read` | List proxies and view their configuration, without credentials. |
+| `proxies:read` | List proxies and reserved ISP IPs, and view proxy configuration without credentials. Reserved IP inventory requires a grant on the whole project or organization. |
| `proxies:use` | Select saved proxies for browsers and run health checks. |
-| `proxies:write` | Rename proxies. |
+| `proxies:write` | Rename proxies and move ISP proxies to reserved exit IPs. |
| `proxies:delete` | Delete proxies. |
## Operations that need more than one action
@@ -64,7 +64,8 @@ Resources must be inside the new key's organization, or its project for a projec
| Any operation on an existing browser | The browser action, plus `profiles:use` on its attached profile and `vaults:use` on every linked vault. If the browser saves profile changes, `profiles:write` on that profile too. |
| Update a browser | `browsers:write` and `browsers:use`, plus `use` on any profile, vault, or proxy the update adds. |
| Fill from a vault | `vaults:use` on the vault and `browsers:use` on the browser. |
-| Add or remove a card in a Kernel wallet (`add_payment_method`, `remove_payment_method`) | `vaults:use` and `vaults:write` on the vault. Without `vaults:write`, the wallet doesn't list either operation in `available_operations`, and invoking one returns `403` with `insufficient_scope`. Other wallet and purchase operations need only `vaults:use`. |
+| Replace a customer-refreshed wallet's access token (`update_access_token`) | `vaults:use` and `vaults:write` on the vault. |
+| Add or remove a card in a Kernel wallet (`add_payment_method`, `remove_payment_method`) | `vaults:use` and `vaults:write` on the vault. Without `vaults:write`, the wallet doesn't list either operation in `available_operations`, and invoking one returns `403` with `insufficient_scope`. |
| Run a proxy health check | `proxies:use` and `proxies:read`. |
List endpoints return only the objects the key can access, so a key without a matching grant gets an empty list.
diff --git a/integrations/wallets/bring-your-own-vgs-vault.mdx b/integrations/wallets/bring-your-own-vgs-vault.mdx
index 00e8b92e..ee7fac0e 100644
--- a/integrations/wallets/bring-your-own-vgs-vault.mdx
+++ b/integrations/wallets/bring-your-own-vgs-vault.mdx
@@ -3,27 +3,27 @@ title: "Bring Your Own VGS Vault"
description: "Pay with cards stored in your own VGS vault without copying them to KERNEL"
---
-use `provider: "vgs"` when your cards already live in your own [VGS](https://www.verygoodsecurity.com) vault and you want KERNEL browsers to pay with them. your vault stays the source of truth: you give KERNEL a read-only VGS service account, and each purchase carries the card's aliases from your vault.
+use `provider: "vgs"` when your cards already live in your own [VGS](https://www.verygoodsecurity.com) vault and you want KERNEL browsers to pay with them. your vault stays the source of truth: you give KERNEL a read-only VGS service account, and each purchase supplies either a full card's aliases or a fresh CVC alias for a card the merchant already holds on file.
-neither your application, KERNEL's api, the agent, nor the browser receives the card number or security code. at authorization, KERNEL reads your aliases through its own VGS outbound proxy, which replaces the revealed values with aliases in KERNEL's VGS vault before they reach KERNEL. at checkout, KERNEL's VGS proxy reveals the card to the merchant.
+neither your application, KERNEL's api, the agent, nor the browser receives the card number or security code. at authorization, KERNEL reads the supplied alias or aliases through its own VGS outbound proxy, which replaces the revealed values with aliases in KERNEL's VGS vault before they reach KERNEL. at checkout, KERNEL's VGS proxy reveals those values to the merchant.
- `provider: "vgs"` requires `@onkernel/sdk` 0.141.0 or later, or `kernel` 0.141.0 or later for Python. the CLI doesn't support VGS provider configurations or cards yet.
+ `provider: "vgs"` requires `@onkernel/sdk` 0.141.0 or later, or `kernel` 0.141.0 or later for Python. `card_mode: "cvc_only"` requires SDK version 0.143.0 or later. the CLI doesn't support VGS provider configurations or cards yet.
## How it works
1. **register your service account once.** create a provider configuration with a VGS service account that can read aliases from your vault.
2. **create a wallet.** the wallet binds that configuration and connects immediately. it holds no cards.
-3. **create a card for each purchase.** send the card's PAN alias, a freshly collected CVC alias, the expiry, and the last four digits.
-4. **authorize.** KERNEL reveals both aliases from your vault through its VGS proxy and stores KERNEL-vault aliases. the card becomes `ready` with no further cardholder approval.
-5. **fill and submit.** `fill` types KERNEL's aliases into the checkout. when the browser submits, KERNEL's VGS proxy replaces them with the real card details on the way to the merchant.
+3. **create a card for each purchase.** for a full-card checkout, send the PAN alias, a freshly collected CVC alias, the expiry, and the last four digits. if the merchant already holds the card and asks only for its security code, set `card_mode: "cvc_only"` and send only the fresh CVC alias.
+4. **authorize.** KERNEL reveals the supplied alias or aliases from your vault through its VGS proxy and stores KERNEL-vault aliases. the card becomes `ready` with no further cardholder approval.
+5. **fill and submit.** `fill` types the full card fields or only the CVC into the checkout. when the browser submits, KERNEL's VGS proxy replaces the aliases with the real values on the way to the merchant.
## Before you start
- **enable alias reveal on your vault.** ask VGS to enable the `aliases:read` capability and alias reveal on your vault. it's off by default.
- **create a read-only service account.** in your VGS dashboard, create a service account with only the `aliases:read` scope. KERNEL uses it to reveal aliases; it doesn't need write or card management scopes. you can revoke or rotate it at any time; once you do, KERNEL can't authorize new cards from your vault.
-- **store cards as aliases.** store the card number as a persistent alias. any alias format works; KERNEL chooses the format of its own aliases.
+- **store card numbers as aliases for full-card purchases.** store the card number as a persistent alias. any alias format works; KERNEL chooses the format of its own aliases. a `cvc_only` purchase doesn't send the card number because the merchant already holds it.
- **collect a fresh CVC for each purchase.** card networks don't allow storing a security code after authorization, so collect it again for each purchase, for example with a VGS Collect CVC field. store it as a volatile alias and create the card item right after collection. the alias must still exist when you authorize.
create a project-scoped client and vault. the examples below use these `kernel` and `vault` variables.
@@ -133,11 +133,11 @@ the wallet's `state.status` is `connected` on creation. it has no enrollment act
1. attach the vault when you [create the browser](/vaults/overview#scope-and-attachment).
2. collect a fresh CVC from the cardholder and store it as a volatile alias in your vault.
-3. create a `card` item with the wallet, the merchant, amount, and currency, and the card's aliases in `spec.card`.
+3. create a `card` item with the wallet, merchant, amount, currency, and the required `spec.card` fields. use the default `full` mode for the whole card, or `cvc_only` when the merchant already holds the card and its checkout asks only for the security code.
4. invoke `authorize`. it takes no parameters and returns the card `ready`, with no action for the cardholder.
5. [fill the checkout](/vaults/fill) before `expires_at`, submit once, and verify the merchant's order record. a completed fill isn't proof of payment.
-the examples create and authorize a card for 452.10 usd.
+the examples create and authorize a full card for 452.10 usd. `full` is the default when you omit `card_mode`.
@@ -210,16 +210,39 @@ if card.state.status != "ready":
-| `spec.card` field | description |
-| --- | --- |
-| `pan_alias` | a persistent alias of the card number in your vault |
-| `cvc_alias` | a volatile alias of a freshly collected security code. it must still exist when `authorize` runs |
-| `exp_month`, `exp_year` | the card's expiry. an alias doesn't carry it |
-| `last4` | the card number's last four digits. KERNEL checks it against the revealed card |
+when the merchant already stores the card and the checkout asks only for its security code, replace the full-card fields inside `spec` with the `cvc_only` shape below. then authorize it with the same operation shown above.
+
+
+
+```typescript TypeScript
+card_mode: 'cvc_only',
+card: {
+ cvc_alias: collectedCvcAlias,
+},
+```
+
+```python Python
+"card_mode": "cvc_only",
+"card": {
+ "cvc_alias": collected_cvc_alias,
+},
+```
+
+
+
+| `spec` field | `full` mode | `cvc_only` mode |
+| --- | --- | --- |
+| `card_mode` | omit it or set `full`; reads omit it | set `cvc_only`; reads return it |
+| `card.pan_alias` | required: a persistent alias of the card number | rejected |
+| `card.cvc_alias` | required: a volatile alias of a freshly collected security code | required: the only card field you send |
+| `card.exp_month`, `card.exp_year` | required: the card's expiry | rejected |
+| `card.last4` | required: KERNEL checks it against the revealed card | rejected |
`spec.card` is write-only. KERNEL stores it encrypted and never returns it in the item, its events, or logs. a repeated `PUT` with the same key must send the same `card`, and card updates return `409`; to change anything, delete the item and create a new one.
-once `ready`, the card supports `fill` on `merchant_url`'s origin until `expires_at`, five minutes after authorization by default. it then becomes `expired`, and you need a new card item with a freshly collected CVC. supported fill fields are `number`, `cvc`, `exp_month`, `exp_year`, and `expiration`; billing fields aren't available for VGS cards. the item's `masks.last4` shows the card's last four digits; the card brand isn't reported.
+once `ready`, either mode supports `fill` on `merchant_url`'s origin until `expires_at`, five minutes after authorization by default. it then becomes `expired`, and you need a new card item with a freshly collected CVC. a full card supports `number`, `cvc`, `exp_month`, `exp_year`, and `expiration`; its `masks.last4` shows the card's last four digits. a `cvc_only` card supports only `cvc` and reports no masks. billing fields aren't available for VGS cards, and the card brand isn't reported.
+
+`fill` stays restricted to `merchant_url`'s origin, but `cvc_only` doesn't narrow the payment window's network scope. during either mode's payment window, KERNEL routes all browser egress through its VGS outbound proxy. the proxy can reveal any supplied alias to any destination the browser sends it to, not only the CVC field or the merchant origin.
the recorded amount and merchant don't restrict the payment requests the browser sends. `expires_at` isn't a single-use guarantee.
@@ -236,8 +259,8 @@ if `authorize` fails, KERNEL stores nothing from your vault. the card's `state.s
## What KERNEL stores
-- **card number:** KERNEL keeps an alias of the card number in its own VGS vault. it remains there after the card item expires or is deleted.
-- **security code:** KERNEL keeps a volatile alias of the security code, which VGS deletes automatically within an hour.
+- **card number:** for a full card, KERNEL keeps an alias of the card number in its own VGS vault. it remains there after the card item expires or is deleted. a `cvc_only` card stores no card-number alias in KERNEL's vault.
+- **security code:** in either mode, KERNEL keeps a volatile alias of the security code, which VGS deletes automatically within an hour.
- **your aliases and credentials:** your aliases and service account secret are encrypted at rest and never returned.
KERNEL never receives the card number or security code itself. to stop KERNEL from reading your vault, revoke the service account in VGS and delete the provider configuration. deletion returns `409` while any item still references it.
diff --git a/integrations/wallets/overview.mdx b/integrations/wallets/overview.mdx
index 93d3e529..143b511c 100644
--- a/integrations/wallets/overview.mdx
+++ b/integrations/wallets/overview.mdx
@@ -48,9 +48,11 @@ not an end user's wallet grant. credentials are encrypted at rest; secrets are
never returned.
with your own client, you can connect users in your backend before creating
-wallets in KERNEL. link imports the user's oauth access and refresh tokens;
-agentcard imports the connected user's `user_id` after validating provider
-access under your configuration, without another hosted enrollment action.
+wallets in KERNEL. link imports the user's oauth grant: KERNEL rotates its
+refresh token by default, or your backend can retain rotation and replace the
+stored access token. agentcard imports the connected user's `user_id` after
+validating provider access under your configuration, without another hosted
+enrollment action.
configurations are organization-scoped and shared across projects, unlike
project-scoped vaults. create, update, and delete require organization-scoped
diff --git a/integrations/wallets/stripe-link.mdx b/integrations/wallets/stripe-link.mdx
index d4b92c7e..3fe0018a 100644
--- a/integrations/wallets/stripe-link.mdx
+++ b/integrations/wallets/stripe-link.mdx
@@ -3,7 +3,7 @@ title: "Link by Stripe"
description: "use link by stripe to approve a one-use payment credential for a browser checkout"
---
-[link by stripe](https://stripe.com/payments/link) connects a user's wallet through oauth and issues a payment credential for an approved purchase: a merchant-bound link pay token on supported stripe checkouts, or a single-use virtual card everywhere else. KERNEL inspects the checkout and chooses which one; you never choose or handle it. KERNEL's native integration handles wallet connection, token refresh, approval actions, and card storage through vault items. oauth credentials and card material use kms-backed envelope encryption.
+[link by stripe](https://stripe.com/payments/link) connects a user's wallet through oauth and issues a payment credential for an approved purchase: a merchant-bound link pay token on supported stripe checkouts, or a single-use virtual card everywhere else. KERNEL inspects the checkout and chooses which one; you never choose or handle it. KERNEL's native integration handles wallet connection, approval actions, and card storage through vault items. by default, it also rotates refresh tokens; your backend can keep refresh-token rotation when it imports a grant. oauth credentials and card material use kms-backed envelope encryption.
use KERNEL's [`fill` api](/vaults/fill) to safely inject the approved card's values into an attached browser without passing them through your application's injection code or model context. you provide references and selectors; KERNEL returns outcomes, not the values. link supplies the card, while `fill` is a KERNEL operation shared with other supported vault items.
@@ -110,10 +110,11 @@ obtain its access and refresh tokens. keep the client secret, pkce verifier, and
tokens out of agent context, browser code, urls, and logs.
create the wallet with the project-scoped `kernel` client and `vault` from
-[Before you start](#before-you-start). supply the access and refresh tokens from
-the same grant, with a currently valid access token. CLI users should restore
-their project-scoped `KERNEL_API_KEY` before running the wallet command and put
-the grant in this protected file:
+[Before you start](#before-you-start). the example uses the default
+`token_refresh: "kernel"` mode, so supply the access and refresh tokens from the
+same grant, with a currently valid access token. CLI users should restore their
+project-scoped `KERNEL_API_KEY` before running the wallet command and put the
+grant in this protected file:
```json link-grant.json
{
@@ -176,15 +177,28 @@ kernel vaults wallets create user-12345 link-wallet --provider link \
a successful import returns a `connected` wallet without a `link_oauth` action.
-KERNEL takes over refresh-token rotation, so your backend must stop refreshing
-that grant. continue with [Select a payment method](#select-a-payment-method).
+with the default `token_refresh: "kernel"`, KERNEL takes over refresh-token
+rotation, so your backend must stop refreshing that grant.
+
+if your backend must retain refresh-token rotation, set
+`authorization.token_refresh` to `"customer"` when you create the wallet, omit
+`refresh_token` from `authorization.tokens`, and keep the refresh token in your
+backend. the setting is fixed at wallet creation, and KERNEL never refreshes or
+revokes that grant. if an operation that reaches link, including creating,
+authorizing, or filling a card, returns `409` with code
+`access_token_rejected`, obtain a fresh access token in your backend, invoke the
+wallet's `update_access_token` operation with it, then retry that operation.
+KERNEL doesn't contact link again with the rejected token.
+`update_access_token` requires `vaults:write` in addition to `vaults:use`.
+
+continue with [Select a payment method](#select-a-payment-method).
## Lifecycle
1. connect a wallet: use KERNEL's client and present the returned `link_oauth` action, or complete oauth in your backend and import the grant with your provider configuration.
2. require the wallet to be `connected`.
3. request the advertised `payment_methods` expansion and let the user choose an eligible method.
-4. reach the final checkout page in a browser with the vault attached, then create a `card` item with the purchase details. creating the card doesn't contact link. to change the details before authorizing, update the card.
+4. reach the final checkout page in a browser with the vault attached, then create a `card` item with the purchase details. to change the details before authorizing, update the card.
5. retrieve the card, verify that it advertises `authorize`, and after explicit user approval invoke it with the browser's `browser_id` and the checkout's exact current top-level `page_url`. KERNEL inspects that checkout, chooses a link pay token or a virtual card, and sends the user an approval request.
6. complete the returned `spend_approval` or `push_approval` action and wait for `state.status` to become `ready`.
7. require the advertised `fill` operation and follow its description: a link pay token takes `browser_id` and `page_url` only, and a virtual card also takes field/selector bindings. inspect the fill outcome, then submit the checkout yourself once.
@@ -684,12 +698,16 @@ wallet and card items accept these `spec` fields. fields not listed here are rej
| item | required fields | optional fields |
| --- | --- | --- |
-| wallet | `provider: 'link'`, `authorization.method: 'oauth'`, `authorization.client` | write-only `authorization.tokens` is required only with a customer-managed client |
+| wallet | `provider: 'link'`, `authorization.method: 'oauth'`, `authorization.client` | `authorization.token_refresh` for a customer-managed client; write-only `authorization.tokens` is required only with a customer-managed client |
| card | `provider`, `wallet`, `payment_method_id`, `amount`, `currency`, `merchant_name`, `merchant_url`, `context` | `line_items`, `totals`, `metadata` |
the default link client is `{type: 'kernel_managed'}`. for your own client, set
-`authorization.client` to `{type: 'customer_managed', provider_config: {name: 'checkout-link'}}`
-and supply `authorization.tokens` with `access_token` and `refresh_token`.
+`authorization.client` to `{type: 'customer_managed', provider_config: {name: 'checkout-link'}}`.
+with the default `authorization.token_refresh: 'kernel'`, supply
+`authorization.tokens` with `access_token` and `refresh_token`. with
+`authorization.token_refresh: 'customer'`, supply only `access_token`. if an
+operation that reaches link returns `409` with code `access_token_rejected`, use
+`update_access_token` to store a fresh access token, then retry that operation.
`amount` uses minor currency units. link accepts 1–500000, and a virtual card allows at most 50000. link 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`. the card item's `expires_at` comes from link: the spend request's expiry, or the issued virtual card's validity. when link reports no expiry for a link pay token request, `fill` stays available for 30 minutes after authorization, including the time the user takes to approve.
diff --git a/proxies/isp.mdx b/proxies/isp.mdx
index 91e729ec..2e692246 100644
--- a/proxies/isp.mdx
+++ b/proxies/isp.mdx
@@ -6,7 +6,7 @@ ISP (Internet Service Provider) proxies are hosted on datacenter infrastructure
## IP rotation behavior
-ISP proxies provide a **static exit IP that persists across sessions** — every tab, request, reconnection, and future browser session attached to this proxy exits through the same IP. The IP only changes in rare ISP-initiated replacement events.
+ISP proxies provide a **static exit IP that persists across sessions** — every tab, request, reconnection, and future browser session attached to this proxy exits through the same IP. The primary IP stays fixed unless you move a proxy backed by a reserved ISP allocation or an ISP initiates a replacement.
This makes ISP proxies suitable for use cases that require a stable IP, such as IP allowlists or [managed auth](/auth/managed-auth) health checks. For comparison with other proxy types, see [IP rotation behavior across proxy types](/proxies/overview).
@@ -19,7 +19,7 @@ For some organizations, Kernel attaches a backup ISP endpoint from a different p
Create an ISP proxy with an exit IP in Singapore:
-ISP proxies support country targeting in the United States (`US`), Singapore (`SG`), the United Kingdom (`GB`), France (`FR`), and Germany (`DE`). If you omit `country`, the proxy defaults to `US`.
+ISP proxies support country targeting in the United States (`US`), Singapore (`SG`), the United Kingdom (`GB`), France (`FR`), and Germany (`DE`). If you omit `country`, the proxy defaults to `US` unless you set a reserved `ip_address`, in which case it uses that IP's country.
@@ -96,7 +96,8 @@ func main() {
## Configuration parameters
-- **`country`** (optional) - ISO 3166 country code. Supported values are `US`, `SG`, `GB`, `FR`, and `DE`. Defaults to `US`.
+- **`country`** (optional) - ISO 3166 country code. Supported values are `US`, `SG`, `GB`, `FR`, and `DE`. Defaults to `US`, or to the country of `ip_address` when you set one.
+- **`ip_address`** (optional) - An active IP from your organization's reserved ISP allocation. Set it to pin the proxy to that exit IP; if you also set `country`, they must match. Omit it to let Kernel choose an IP.
- **`bypass_hosts`** (optional) - Array of hostnames that bypass the proxy and connect directly (max 100 entries)
## .gov support
@@ -176,7 +177,7 @@ See the [overview](/proxies/overview#bypass-hosts) for full bypass host rules an
## Getting the static IP address
-Because an ISP proxy has a static exit IP, you can read that IP once and reuse it — for example, to add it to an IP allowlist. The IP is returned as `ip_address` in the response when you [create a proxy](https://kernel.sh/docs/api-reference/proxies/create-a-proxy) and when you [get a proxy by ID](https://kernel.sh/docs/api-reference/proxies/get-proxy-by-id).
+Because an ISP proxy has a static exit IP, you can read that IP once and reuse it while the proxy remains on that IP — for example, to add it to an IP allowlist. The IP is returned as `ip_address` in the response when you [create a proxy](https://kernel.sh/docs/api-reference/proxies/create-a-proxy) and when you [get a proxy by ID](https://kernel.sh/docs/api-reference/proxies/get-proxy-by-id). If you move the proxy to another reserved IP, update any allowlists that use the old address.
```typescript Typescript/Javascript
diff --git a/proxies/overview.mdx b/proxies/overview.mdx
index 217a25c8..7f5d30ab 100644
--- a/proxies/overview.mdx
+++ b/proxies/overview.mdx
@@ -21,7 +21,7 @@ Kernel-provided proxies are unmetered and not billed, subject to the fair use ru
-ISP proxies provide a **static exit IP that persists across sessions** — every browser session attached to the proxy exits through the same IP, and it only changes in rare ISP-initiated replacement events or a temporary [failover to a backup endpoint](/proxies/isp#backup-endpoint-failover). This makes them suitable for IP allowlists or [managed auth](/auth/managed-auth) health checks that must egress from a single IP.
+ISP proxies provide a **static exit IP that persists across sessions** — every browser session attached to the proxy exits through the same IP. The primary IP stays fixed unless you move a proxy backed by a reserved ISP allocation or an ISP initiates a replacement; traffic can temporarily use a different IP during a [failover to a backup endpoint](/proxies/isp#backup-endpoint-failover). This makes ISP proxies suitable for IP allowlists or [managed auth](/auth/managed-auth) health checks that must egress from a single IP.
Residential and mobile proxy exit IPs may change per connection — see [Residential routing and IP behavior](/proxies/residential#routing-and-ip-behavior) and [Mobile Proxies](/proxies/mobile) for details.
diff --git a/vaults/1password.mdx b/vaults/1password.mdx
index 98acf408..ec1f651b 100644
--- a/vaults/1password.mdx
+++ b/vaults/1password.mdx
@@ -30,7 +30,7 @@ KERNEL-hosted collection.
| human step | enter values in a KERNEL-hosted form | consent once when the account is linked, then approve each access request in the 1Password app |
| browser operation | [`fill`](/vaults/fill): your controller supplies field names and selectors; or [`webmcp_invoke`](/vaults/credentials#use-a-credential-with-webmcp) when advertised | `1pw_fill`: the 1Password extension selects the fields; you supply the page url |
| submission | `fill` never submits the form; a WebMCP tool may submit or have other side effects | the extension fills and submits the form; if it can't submit, it clears what it filled |
-| destination check | your controller authorizes the destination | the page must share the origin of an approved login |
+| destination check | your controller authorizes the destination | the 1Password extension decides whether the approved login matches the page and refuses pages that don't |
| supported logins | any fields you define | logins with a username, password, and one-time password, stored in the owner's own non-shared vault; no shared-vault items or passkeys |
| totp | KERNEL generates codes from a stored seed | 1Password fills the login's stored one-time password; when the site asks for it on a later page, invoke `1pw_fill` again there |
| availability | preview | generally available |
@@ -59,7 +59,7 @@ before you plan a rollout.
- the user chooses the exact logins to grant in the 1Password app.
- KERNEL stores encrypted connection material and credential references in its items, not the login values.
- the 1Password extension fetches the credential and fills it into the page. KERNEL's api returns a status, never the values.
-- KERNEL checks that the page shares the origin of an approved login before it fills.
+- the 1Password extension decides whether an approved login matches the page and refuses pages that don't.
- KERNEL locks the browser while the values are in the page. see [fill and submit](#fill-and-submit).
- on `fillFailed` or `autosubmitFailed`, the extension clears what it filled before it returns.
- the destination website necessarily receives the credential, and its scripts can observe it.
@@ -479,9 +479,9 @@ create with identical values returns the current item; a different
a `requests` object can hold up to five `login` entries, so one approval can
cover several logins. the user can approve some entries and not others. when
-approved entries share an origin, pass `entry_id` to `1pw_fill` to choose one.
-`reason` and `keywords` overrides at request time work only when the item has a
-single entry.
+several entries are approved and the page's origin doesn't identify exactly one,
+pass `entry_id` to `1pw_fill` to choose one. `reason` and `keywords` overrides at
+request time work only when the item has a single entry.
@@ -686,12 +686,13 @@ KERNEL loads the 1Password extension into it on demand the first time you fill.
navigate it to the page with the username and password form,
not a page that asks the user to choose a sign-in method. then invoke `1pw_fill` with
the browser's session id and the exact current top-level `page_url`. the url
-must match exactly one open page and share the origin of an approved entry.
-when several open pages have that url, for example one tab per task, pass the
-page's CDP target id as `target_id`, such as `tab.target_id` from a
-`playwright.execute` response. KERNEL brings that page to the front of its
-window before filling.
-when more than one approved entry has that origin, pass its `entry_id` from
+must match exactly one open page unless you pass `target_id`. when several open
+pages have that url, for example one tab per task, pass the page's CDP target id
+as `target_id`, such as `tab.target_id` from a `playwright.execute` response.
+KERNEL brings that page to the front of its window before filling.
+the 1Password extension decides whether the approved login matches the page and
+refuses pages that don't. when several entries are approved and the page's origin
+doesn't identify exactly one of them, pass the chosen entry's `entry_id` from
`state.access_request.entries`. the extension selects the fields, fills them,
and submits the form; you can't supply selectors or values.
`fill_submitted` means the extension reported that it submitted the form. if the extension fills
@@ -700,9 +701,9 @@ returns `fill_unknown` with `error_code: "autosubmitFailed"`. the page might alr
on, for example to the next sign-in step. a submitted form doesn't mean the login succeeded.
for a sign-in that spans several pages, such as a username page, a password
-page, and a one-time password page, invoke `1pw_fill` again on each page. each
-page must share the origin of the approved entry, and every fill is a new
-request to 1Password, so an ended grant fails at the next fill.
+page, and a one-time password page, invoke `1pw_fill` again on each page. the
+extension decides whether the approved login matches each page, and every fill
+is a new request to 1Password, so an ended grant fails at the next fill.
**exclusive browser control during autofill:** while `1pw_fill` runs, KERNEL
@@ -782,8 +783,9 @@ if result.status == "fill_failed":
`1pw_fill` submits the form, so the examples turn off sdk retries.
-for an item with one entry, omit `entry_id`. `timeout_ms` accepts 1–30,000 and
-defaults to 30,000.
+`entry_id` is optional when only one entry is approved, or when the page's
+origin identifies exactly one of several approved entries. otherwise, pass it.
+`timeout_ms` accepts 1–30,000 and defaults to 30,000.
| outcome | meaning | next step |
| --- | --- | --- |
@@ -792,9 +794,8 @@ defaults to 30,000.
| `200` `fill_unknown` | the form might have been filled or submitted, but the outcome is inconclusive. `error_code` is `autosubmitFailed` when the extension submitted but couldn't confirm the fill finished | `1pw_fill` can submit the form, so never retry automatically. check the page first before retrying. |
| `400` `page_not_found` or `ambiguous_page` | `page_url` matched no page or more than one, or `target_id` isn't an open page at `page_url` | fix the url, or pass `target_id` to choose among duplicate tabs |
| `400` `invalid_request` | the fill request was rejected as invalid | check `browser_id`, `page_url`, and `timeout_ms` before trying again |
-| `403` `destination_denied` | the page is outside every approved login origin | navigate to a requested website |
| `409` `agentic_mode_unavailable` | the 1Password extension couldn't take control of the page; nothing was filled | `1pw_fill` can submit the form, so never retry automatically. check the page first before retrying. |
-| `409` | the item isn't `ready`, its approval is unusable, `entry_id` is missing or doesn't match an approved entry for the page, or a stored access token has expired | retrieve the item and follow `available_operations` |
+| `409` | the item isn't `ready`, its approval is unusable, `entry_id` is missing or doesn't match an approved entry, or a stored access token has expired | retrieve the item and follow `available_operations` |
### Fall back to KERNEL-hosted collection