diff --git a/profiles/credentials.mdx b/profiles/credentials.mdx
index 7b990ab8..45f164aa 100644
--- a/profiles/credentials.mdx
+++ b/profiles/credentials.mdx
@@ -138,7 +138,7 @@ credential = await kernel.credentials.create(
For sites with "Sign in with Google/GitHub/Microsoft", set `sso_provider` and include the OAuth provider's domains in `allowed_domains`.
-The workflow automatically clicks the matching SSO button and completes OAuth:
+The workflow automatically selects the matching SSO provider and completes the OAuth flow:
```typescript TypeScript
diff --git a/profiles/managed-auth/faq.mdx b/profiles/managed-auth/faq.mdx
index 777034b5..fa59c76d 100644
--- a/profiles/managed-auth/faq.mdx
+++ b/profiles/managed-auth/faq.mdx
@@ -10,26 +10,60 @@ When you link credentials to a connection, Kernel monitors the login session and
Automatic re-authentication only works when the stored credentials are complete and don't require human input. If login needs SMS/email OTP, push notifications, or manual MFA selection, you'll need to trigger a new login session manually.
+## How do I trigger re-authentication manually?
+
+Call `.login()` on an existing connection to trigger re-authentication without waiting for the next health check:
+
+
+```typescript TypeScript
+const login = await kernel.auth.connections.login(auth.id);
+```
+
+```python Python
+login = await kernel.auth.connections.login(auth.id)
+```
+
+
+If the connection is already authenticated, this returns quickly. If the login flow requires human input (e.g., MFA), handle it via the [Hosted UI](/profiles/managed-auth/hosted-ui) or [Programmatic](/profiles/managed-auth/programmatic) flow.
## How often are health checks performed?
-Health checks on regular cadences based on your plan:
+Health checks run on regular cadences based on your plan:
- Hobbyist (1 hr)
- Start-Up (15 min)
- Enterprise (configurable)
-## How do I know if a Kernel can automatically re-authenticate a connection?
+You can configure the health check interval when creating or updating a connection:
+
+
+```typescript TypeScript
+await kernel.auth.connections.update(auth.id, {
+ health_check_interval: 600, // 10 minutes (300–86400 seconds)
+});
+```
+
+```python Python
+await kernel.auth.connections.update(
+ auth.id,
+ health_check_interval=600, # 10 minutes (300–86400 seconds)
+)
+```
+
+
+The interval must be between 300 seconds (5 minutes) and 86400 seconds (24 hours). Changes take effect immediately.
+
+## How do I know if Kernel can automatically re-authenticate a connection?
Check the `can_reauth` field on a connection. This boolean checks the following conditions:
1. **Credential linked** — A credential must be attached to the connection (stored in Kernel or via an external provider like [1Password](/integrations/1password))
-2. **No external action required** — The learned login flow doesn't require human intervention
+2. **No external action required** — The login flow doesn't require human intervention
Only if all of the above conditions are met will `can_reauth` be `true`. When true, Kernel will attempt to automatically re-authenticate the connection.
### External actions that prevent auto-reauth
-After a successful login, Kernel saves the login flow. If the flow includes steps that require human action—like SMS/email OTP, push notifications, or manual MFA selection—Kernel marks the connection as unable to auto-reauth because those steps can't be automated without user input.
+After a successful login, Kernel records what the login flow required. If the flow includes steps that require human action — like SMS/email OTP, push notifications, or manual MFA selection — Kernel marks the connection as unable to auto-reauth because those steps can't be automated without user input.
If your login flow requires one of these, you can still automate around it:
- **Switch to TOTP** — If the site supports authenticator apps, add a `totp_secret` to your credential. TOTP codes are generated automatically, so the login flow won't require external action.
@@ -37,23 +71,57 @@ If your login flow requires one of these, you can still automate around it:
## Which authentication methods are supported?
-Managed Auth supports username/password authentication and most SSO providers.
+Managed Auth supports username/password authentication and most SSO providers. The CUA-based login agent handles complex login flows including multi-step logins, popups, CAPTCHAs, and account selection screens.
+
+Tested sites include: LinkedIn, Google, Instacart, X/Twitter, Airbnb, PayPal, Netflix, Amazon, Walmart, and many more.
-Passkey-based authentication (e.g., Google accounts with passkeys enabled) is not currently supported. If a user's SSO provider requires a passkey, the login will fail.
+Passkey-based authentication (e.g., hardware security keys, device-bound passkeys) is not currently supported. If a login flow requires a passkey, it will fail with the `unsupported_auth_method` error code.
## What happens if login fails?
-If a login attempt fails, Kernel will retry with exponential backoff. After multiple failures, the [login flow](/profiles/managed-auth/hosted-ui) will be marked as failed and you'll receive an error. Common failure reasons include:
+If a login attempt fails, the connection status changes to `NEEDS_AUTH` and the flow includes an error code and message. Common error codes include:
+
+| Error code | Description |
+|------------|-------------|
+| `domain_not_allowed` | Login redirected to a domain not in `allowed_domains` |
+| `network_error` | DNS or connection failure, possibly a proxy issue |
+| `unsupported_auth_method` | Login requires a passkey or security key |
+| `awaiting_input_timeout` | User didn't provide input in time |
+| `external_action_timeout` | Push notification or app approval wasn't completed in time |
+| `max_steps_exceeded` | Login flow exceeded the maximum step count |
-- Invalid credentials
-- Bot detection blocking the login page
-- CAPTCHAs that couldn't be solved
+Timeouts (`awaiting_input_timeout`, `external_action_timeout`) are recoverable — start a new login session to retry.
## Can I use Managed Auth with any website?
-Managed Auth works with most websites. Sites with aggressive bot detection may require additional configuration (stealth mode, proxies). Passkeys and hardware security keys are not currently supported.
+Managed Auth works with most websites. The CUA-based agent visually navigates login pages, which makes it more resilient to different page layouts and complex flows than traditional DOM-based approaches. Sites with aggressive bot detection may require additional configuration (stealth mode, proxies). Passkeys and hardware security keys are not currently supported.
+
+## Can I update a connection after creating it?
+
+Yes. Use the update endpoint to change configuration without recreating the connection:
+
+
+```typescript TypeScript
+await kernel.auth.connections.update(auth.id, {
+ login_url: 'https://example.com/signin',
+ health_check_interval: 900,
+ allowed_domains: ['example.com', 'accounts.google.com'],
+});
+```
+
+```python Python
+await kernel.auth.connections.update(
+ auth.id,
+ login_url="https://example.com/signin",
+ health_check_interval=900,
+ allowed_domains=["example.com", "accounts.google.com"],
+)
+```
+
+
+All fields are optional — only provided fields are changed.
## How is Managed Auth billed?
diff --git a/profiles/managed-auth/hosted-ui.mdx b/profiles/managed-auth/hosted-ui.mdx
index 7f9b2714..8d7c39bc 100644
--- a/profiles/managed-auth/hosted-ui.mdx
+++ b/profiles/managed-auth/hosted-ui.mdx
@@ -131,7 +131,7 @@ Managed Auth Connections are generated using Kernel's [stealth](/browsers/bot-de
-## Complete Example
+## Complete example
```typescript TypeScript
@@ -204,17 +204,17 @@ if state.status == "AUTHENTICATED":
```
-## Adding Features
+## Adding features
The basic flow above gets you started. Add these features as needed:
-### Credentials and Auto-Reauth
+### Credentials and auto-reauth
Credentials are saved after every successful login, enabling automatic re-authentication when the session expires. One-time codes (TOTP, SMS, etc.) are not saved.
To opt out of credential saving, set `save_credentials: false` when creating the connection. See [Credentials](/profiles/credentials) for more on automated authentication.
-### Custom Login URL
+### Custom login URL
If the site's login page isn't at the default location, specify it when creating the connection:
@@ -236,7 +236,7 @@ auth = await kernel.auth.connections.create(
```
-### SSO/OAuth Support
+### SSO/OAuth support
Sites with "Sign in with Google/GitHub/Microsoft" are supported. The user completes the OAuth flow with the provider, and the authenticated session is automatically saved to the Kernel profile.
@@ -260,9 +260,48 @@ auth = await kernel.auth.connections.create(
```
-### Post-Login URL
+### Health check interval
-After successful authentication, `post_login_url` will be set to the page where the login landed. Use this start your automation from the right place:
+Configure how often Kernel checks that the login session is still valid:
+
+
+```typescript TypeScript
+const auth = await kernel.auth.connections.create({
+ domain: 'example.com',
+ profile_name: 'my-profile',
+ health_check_interval: 900, // 15 minutes (300–86400 seconds)
+});
+```
+
+```python Python
+auth = await kernel.auth.connections.create(
+ domain="example.com",
+ profile_name="my-profile",
+ health_check_interval=900, # 15 minutes (300–86400 seconds)
+)
+```
+
+
+You can also update the health check interval on an existing connection:
+
+
+```typescript TypeScript
+await kernel.auth.connections.update(auth.id, {
+ health_check_interval: 600, // 10 minutes
+});
+```
+
+```python Python
+await kernel.auth.connections.update(
+ auth.id,
+ health_check_interval=600, # 10 minutes
+)
+```
+
+
+### Post-login URL
+
+After successful authentication, `post_login_url` will be set to the page where the login landed. Use this to start your automation from the right place:
```typescript TypeScript
diff --git a/profiles/managed-auth/overview.mdx b/profiles/managed-auth/overview.mdx
index e64f65fc..f423b463 100644
--- a/profiles/managed-auth/overview.mdx
+++ b/profiles/managed-auth/overview.mdx
@@ -3,17 +3,15 @@ title: "Overview"
description: "Create authenticated browser sessions for your automations"
---
-
- Managed Auth is currently in public beta. Features are subject to change.
-
-
Managed Auth creates and maintains authenticated browser profiles for your AI agents and web automations. Store credentials once, and Kernel re-authenticates automatically when needed. When you launch a browser with the managed profile, you're already logged in and ready to go.
-## How It Works
+Managed Auth uses a Computer Use Agent (CUA) under the hood — an AI that visually navigates login pages using screenshots, just like a human would. This means it handles complex login flows automatically: multi-step logins, popups, CAPTCHAs, and account selection screens.
+
+## How it works
- A **Managed Auth Connection** links a profile to a website domain. Create one for each domain + profile combination you want to keep authenticated.
+ A **Managed Auth Connection** associates a profile to a website domain. Create one for each domain + profile combination you want to keep authenticated.
```typescript TypeScript
@@ -33,7 +31,7 @@ auth = await kernel.auth.connections.create(
A **Managed Auth Session** is the corresponding login flow for the specified connection. Users provide credentials via a Kernel-hosted page or your own UI.
-
+
Specify a [Credential](/profiles/credentials) to enable re-authentication without user input.
@@ -101,7 +99,7 @@ await page.goto("https://netflix.com")
-## Choose Your Integration
+## Choose your integration
@@ -112,7 +110,7 @@ await page.goto("https://netflix.com")
**Full control** - Custom UI or headless
- Build your own credential collection. Handle login fields, SSO buttons, MFA selection, and external actions (push notifications, security keys).
+ Build your own credential collection. Handle login fields, SSO buttons, MFA selection, sign-in options, and external actions (push notifications, security keys).
@@ -121,12 +119,39 @@ await page.goto("https://netflix.com")
The most valuable workflows live behind logins. Managed Auth provides:
-- **Works on any website** - Login pages are discovered and handled automatically
-- **SSO/OAuth support** - "Sign in with Google/GitHub/Microsoft" buttons work out-of-the-box via `allowed_domains`
-- **2FA/OTP handling** - TOTP codes automated, SMS/email/push OTP are supported
-- **Post-login URL** - Get the URL where login landed (`post_login_url`) so you can start automations from the right page
-- **Session monitoring** - Automatic re-authentication when sessions expire with stored credentials
-- **Secure by default** - Credentials encrypted at rest, never exposed in API responses, or passed to LLMs
+- **Works on any website** — The CUA visually navigates login pages, handling complex flows including multi-step logins, popups, and account selection screens
+- **SSO/OAuth support** — "Sign in with Google/GitHub/Microsoft" buttons work out-of-the-box via `allowed_domains`
+- **2FA/OTP handling** — TOTP codes automated, SMS/email/push OTP supported
+- **Post-login URL** — Get the URL where login landed (`post_login_url`) so you can start automations from the right page
+- **Session monitoring** — Automatic re-authentication when sessions expire with stored credentials
+- **Health checks** — Periodic verification that login sessions are still valid, with configurable intervals
+- **Secure by default** — Credentials encrypted at rest, never exposed in API responses, or passed to LLMs
+- **Learns from experience** — The CUA generates reusable skills from successful logins, making subsequent logins faster and more reliable
+
+## Updating a connection
+
+After creating a connection, you can update its configuration without recreating it:
+
+
+```typescript TypeScript
+const updated = await kernel.auth.connections.update(auth.id, {
+ login_url: 'https://netflix.com/login',
+ health_check_interval: 900, // 15 minutes
+ allowed_domains: ['netflix.com', 'accounts.google.com'],
+});
+```
+
+```python Python
+updated = await kernel.auth.connections.update(
+ auth.id,
+ login_url="https://netflix.com/login",
+ health_check_interval=900, # 15 minutes
+ allowed_domains=["netflix.com", "accounts.google.com"],
+)
+```
+
+
+All fields are optional — only provided fields are changed. The `health_check_interval` update takes effect immediately without restarting the connection.
## Security
diff --git a/profiles/managed-auth/programmatic.mdx b/profiles/managed-auth/programmatic.mdx
index 3dc77e69..93bd9d57 100644
--- a/profiles/managed-auth/programmatic.mdx
+++ b/profiles/managed-auth/programmatic.mdx
@@ -10,7 +10,7 @@ Use the Programmatic flow when:
- You're building headless/automated authentication
- You have credentials stored and want to authenticate without user interaction
-## How It Works
+## How it works
@@ -58,7 +58,7 @@ login = await kernel.auth.connections.login(auth.id)
Credentials are saved automatically on successful login, enabling automatic re-authentication when the session expires.
-### 3. Poll and Submit Credentials
+### 3. Poll and submit credentials
A single loop handles everything—initial login, 2FA, and completion:
@@ -72,7 +72,7 @@ while (state.flow_status === 'IN_PROGRESS') {
const fieldValues = getCredentialsForFields(state.discovered_fields);
await kernel.auth.connections.submit(auth.id, { fields: fieldValues });
}
-
+
await new Promise(r => setTimeout(r, 2000));
state = await kernel.auth.connections.retrieve(auth.id);
}
@@ -90,7 +90,7 @@ while state.flow_status == "IN_PROGRESS":
if state.flow_step == "AWAITING_INPUT" and state.discovered_fields:
field_values = get_credentials_for_fields(state.discovered_fields)
await kernel.auth.connections.submit(auth.id, fields=field_values)
-
+
await asyncio.sleep(2)
state = await kernel.auth.connections.retrieve(auth.id)
@@ -109,7 +109,7 @@ The `discovered_fields` array tells you what the login form needs:
[{ name: 'otp', type: 'code' }]
```
-## Complete Example
+## Complete example
```typescript TypeScript
@@ -132,7 +132,7 @@ while (state.flow_status === 'IN_PROGRESS') {
if (state.flow_step === 'AWAITING_INPUT' && state.discovered_fields?.length) {
// Check what fields are needed
const fieldNames = state.discovered_fields.map(f => f.name);
-
+
if (fieldNames.includes('username')) {
// Initial login
await kernel.auth.connections.submit(auth.id, {
@@ -153,12 +153,12 @@ while (state.flow_status === 'IN_PROGRESS') {
if (state.status === 'AUTHENTICATED') {
console.log('Authentication successful!');
-
+
const browser = await kernel.browsers.create({
profile: { name: 'github-profile' },
stealth: true,
});
-
+
// Navigate to the site—you're already logged in
await page.goto('https://github.com');
}
@@ -185,7 +185,7 @@ while state.flow_status == "IN_PROGRESS":
if state.flow_step == "AWAITING_INPUT" and state.discovered_fields:
# Check what fields are needed
field_names = [f["name"] for f in state.discovered_fields]
-
+
if "username" in field_names:
# Initial login
await kernel.auth.connections.submit(
@@ -205,22 +205,22 @@ while state.flow_status == "IN_PROGRESS":
if state.status == "AUTHENTICATED":
print("Authentication successful!")
-
+
browser = await kernel.browsers.create(
profile={"name": "github-profile"},
stealth=True,
)
-
+
# Navigate to the site—you're already logged in
await page.goto("https://github.com")
```
-## Handling Different Input Types
+## Handling different input types
-The basic polling loop handles `discovered_fields`, but login pages can require other input types too.
+The basic polling loop handles `discovered_fields`, but login pages can require other input types too. When polling, check for each input type and submit the appropriate response.
-### SSO Buttons
+### SSO buttons
When the login page has "Sign in with Google/GitHub/Microsoft" buttons, they appear in `pending_sso_buttons`:
@@ -231,10 +231,10 @@ if (state.pending_sso_buttons?.length) {
for (const btn of state.pending_sso_buttons) {
console.log(`${btn.provider}: ${btn.label}`);
}
-
- // Submit the selected SSO button
+
+ // Submit by provider name
await kernel.auth.connections.submit(auth.id, {
- sso_button_selector: state.pending_sso_buttons[0].selector
+ sso_provider: state.pending_sso_buttons[0].provider
});
}
```
@@ -244,20 +244,22 @@ if state.pending_sso_buttons:
# Show the user available SSO options
for btn in state.pending_sso_buttons:
print(f"{btn['provider']}: {btn['label']}")
-
- # Submit the selected SSO button
+
+ # Submit by provider name
await kernel.auth.connections.submit(
auth.id,
- sso_button_selector=state.pending_sso_buttons[0]["selector"],
+ sso_provider=state.pending_sso_buttons[0]["provider"],
)
```
+You can submit SSO selections using either `sso_provider` (provider name like `"google"`, `"github"`) or `sso_button_selector` (XPath selector from the button object). The `sso_provider` field is the recommended approach.
+
Remember to set `allowed_domains` on the connection to include the OAuth provider's domain (e.g., `accounts.google.com`).
-### MFA Selection
+### MFA selection
When the site offers multiple MFA methods, they appear in `mfa_options`:
@@ -268,7 +270,7 @@ if (state.mfa_options?.length) {
for (const opt of state.mfa_options) {
console.log(`${opt.type}: ${opt.label}`);
}
-
+
// Submit the selected MFA method
await kernel.auth.connections.submit(auth.id, {
mfa_option_id: 'sms'
@@ -281,7 +283,7 @@ if state.mfa_options:
# Available types: sms, email, totp, push, call, security_key
for opt in state.mfa_options:
print(f"{opt['type']}: {opt['label']}")
-
+
# Submit the selected MFA method
await kernel.auth.connections.submit(
auth.id,
@@ -292,7 +294,43 @@ if state.mfa_options:
After selecting an MFA method, the flow continues. Poll for `discovered_fields` to submit the code, or handle external actions for push/security key.
-### External Actions (Push, Security Key)
+### Sign-in options
+
+Some login pages present account selection or organization pickers (e.g., "Which account do you want to use?"). These appear in `sign_in_options`:
+
+
+```typescript TypeScript
+if (state.sign_in_options?.length) {
+ // Show available sign-in choices
+ for (const opt of state.sign_in_options) {
+ console.log(`${opt.id}: ${opt.label}`);
+ if (opt.description) console.log(` ${opt.description}`);
+ }
+
+ // Submit the selected option by ID
+ await kernel.auth.connections.submit(auth.id, {
+ sign_in_option_id: state.sign_in_options[0].id
+ });
+}
+```
+
+```python Python
+if state.sign_in_options:
+ # Show available sign-in choices
+ for opt in state.sign_in_options:
+ print(f"{opt['id']}: {opt['label']}")
+ if opt.get("description"):
+ print(f" {opt['description']}")
+
+ # Submit the selected option by ID
+ await kernel.auth.connections.submit(
+ auth.id,
+ sign_in_option_id=state.sign_in_options[0]["id"],
+ )
+```
+
+
+### External actions (push, security key)
When the site requires an action outside the browser (push notification, security key tap), the step becomes `AWAITING_EXTERNAL_ACTION`:
@@ -302,7 +340,7 @@ if (state.flow_step === 'AWAITING_EXTERNAL_ACTION') {
// Show the message to the user
console.log(state.external_action_message);
// e.g., "Check your phone for a push notification"
-
+
// Keep polling—the flow resumes automatically when the user completes the action
}
```
@@ -312,24 +350,40 @@ if state.flow_step == "AWAITING_EXTERNAL_ACTION":
# Show the message to the user
print(state.external_action_message)
# e.g., "Check your phone for a push notification"
-
+
# Keep polling—the flow resumes automatically when the user completes the action
```
-## Step Reference
+## Submit field reference
+
+When `flow_step` is `AWAITING_INPUT`, submit exactly one of these input types:
+
+| Field | Description |
+|-------|-------------|
+| `fields` | Key-value map of credential fields (username, password, OTP code, etc.) |
+| `sso_provider` | SSO provider name (e.g., `"google"`, `"github"`) from `pending_sso_buttons` |
+| `sso_button_selector` | XPath selector of the SSO button to click (from `pending_sso_buttons`) |
+| `mfa_option_id` | ID of the MFA method to select (from `mfa_options`) |
+| `sign_in_option_id` | ID of the sign-in option to select (from `sign_in_options`) |
+
+
+`mfa_option_id` can be combined with `fields` when the MFA option has associated input fields. All other submit types are mutually exclusive.
+
+
+## Step reference
The `flow_step` field indicates what the flow is waiting for:
| Step | Description |
|------|-------------|
| `DISCOVERING` | Finding the login page and analyzing it |
-| `AWAITING_INPUT` | Waiting for field values, SSO button click, or MFA selection |
+| `AWAITING_INPUT` | Waiting for field values, SSO selection, MFA selection, or sign-in option |
| `SUBMITTING` | Processing submitted values |
| `AWAITING_EXTERNAL_ACTION` | Waiting for push approval, security key, etc. |
| `COMPLETED` | Flow has finished |
-## Status Reference
+## Status reference
The `flow_status` field indicates the current flow state:
@@ -348,7 +402,24 @@ The `status` field indicates the overall connection state:
| `AUTHENTICATED` | Profile is logged in and ready to use |
| `NEEDS_AUTH` | Profile needs authentication |
-## Real-Time Updates with SSE
+## Error codes
+
+When a login flow fails, the `error_code` field provides a machine-readable reason:
+
+| Error code | Description |
+|------------|-------------|
+| `domain_not_allowed` | Login redirected to a domain not in `allowed_domains` |
+| `network_error` | DNS or connection failure, possibly a proxy issue |
+| `unsupported_auth_method` | Login requires a passkey or security key (not yet supported) |
+| `awaiting_input_timeout` | User didn't provide input in time |
+| `external_action_timeout` | Push notification or app approval wasn't completed in time |
+| `max_steps_exceeded` | Login flow was too complex and exceeded the maximum step count |
+
+
+`awaiting_input_timeout` and `external_action_timeout` are recoverable — you can start a new login session to retry.
+
+
+## Real-time updates with SSE
For real-time UIs, you can stream login flow events via Server-Sent Events instead of polling: