---
title: Logins, secrets & profiles
description: Store write-only secrets the agent can type but never see, generate two-factor codes, and save sessions so later runs start signed in.
---

The agent can sign in to websites for you without the model ever seeing a password. You store each credential once in the vault, bound to the sites where it may be used, and pass its name to the run. The agent decides where a value goes. The value itself is decrypted and typed only at that moment, and only if the page is on a site you allowed.

| Option | Use it for | Stored |
| --- | --- | --- |
| [Vault secrets](#store-a-secret-in-the-vault) | Passwords, usernames, TOTP seeds you reuse | Encrypted until you delete it |
| [Per-run secrets](#per-run-secrets) | A credential for one run only | Encrypted for the run, deleted when it ends |
| [Profiles](#save-and-reuse-a-session) | Skipping the login on later runs | Cookies and local storage, encrypted |
| [Credentials in the goal](#credentials-in-the-goal) | Throwaway test accounts | In the run's goal text. Not recommended |

## Store a secret in the vault

`PUT /v1/browser/vault/{name}` stores a secret. Sending the same name again replaces it.

```bash title="cURL"
curl -X PUT https://lovelyrouter.com/v1/browser/vault/shop_password \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "value": "correct-horse-battery-staple",
    "kind": "password",
    "origins": ["https://shop.example.com"]
  }'
```

```python title="Python"
requests.put(f"{API}/vault/shop_password", headers=H, json={
    "value": os.environ["SHOP_PASSWORD"],
    "kind": "password",
    "origins": ["https://shop.example.com"],
}).raise_for_status()
```

```js title="Node.js"
await fetch(`${API}/vault/shop_password`, {
  method: 'PUT',
  headers,
  body: JSON.stringify({
    value: process.env.SHOP_PASSWORD,
    kind: 'password',
    origins: ['https://shop.example.com'],
  }),
});
```

The response confirms what was stored, without the value:

```json
{ "name": "shop_password", "kind": "password", "origins": ["https://shop.example.com"], "stored": true }
```

Secrets are write-only: no endpoint returns a value. You can also manage them in the [console](/app/browser) under **Secrets & files**. The Python and Node.js samples use the `API`, `H` and `headers` setup from [Run your first task](/docs/browser-agent#run-your-first-task).

:::params Secret fields
- `name` string, required
  In the path. 1 to 64 characters: lowercase `a-z`, `0-9`, `_`, `.`, `-`, starting with a letter or digit. The agent sees this name, so make it descriptive: `github_password`, not `pw1`.
- `value` string, required
  The secret, up to 4 KB (a larger value returns `413`). For `totp`, the base32 seed.
- `kind` string, default `password`
  What the value is. It decides which fields the agent may type it into.
- `origins` array, required
  1 to 20 sites where the secret may be typed, as `https://host` or `https://*.example.com`. `https` only, except `http://localhost` and `http://127.0.0.1`.
:::

| `kind` | Typed into | Notes |
| --- | --- | --- |
| `password` | Password fields only | |
| `username` | Text, email, phone, search, URL and number fields | Not password fields |
| `text` | Text, email, phone, search, URL and number fields | Any other value: account numbers, answers to security questions |
| `totp` | Text, phone, number and password fields | Stored as the seed. The agent types the current 6-digit code |

### Origins

The agent may type a secret only when the page it is on, as the browser reports it, matches one of the secret's origins:

- `https://shop.example.com` matches that exact site, and only over https.
- `https://*.example.com` matches `example.com` and every subdomain, such as `login.example.com`.

A secret is not even offered to the agent on any other site. If a lookalike phishing page asks for your login, the agent has no way to type the value there.

## Use secrets in a run

Pass the names in `secrets`. The run may use only the secrets you list.

```bash title="cURL"
curl https://lovelyrouter.com/v1/browser/runs \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "goal": "Sign in and tell me the status of my latest order.",
    "start_url": "https://shop.example.com/login",
    "secrets": ["shop_user", "shop_password"]
  }'
```

The trace shows which secret went where, never the value: the step's `action` reads `secret:e4:shop_password`. A name that isn't in your vault returns `400` with `Unknown vault secret(s): shop_pw. Store them with PUT /v1/browser/vault/{name}.`

## Two-factor codes

For sites that use an authenticator app, store the TOTP seed as a `totp` secret. The seed is the text behind the QR code you scan when you turn on two-factor login (often shown as "can't scan the code?"): 16 or more characters of `A`–`Z` and `2`–`7`. Spaces and dashes are ignored.

```bash title="cURL"
curl -X PUT https://lovelyrouter.com/v1/browser/vault/github_totp \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"value": "JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP", "kind": "totp", "origins": ["https://github.com"]}'
```

When the site asks for the code, the agent types the current one (standard 6-digit, 30-second codes). Pass the seed's name in `secrets` with the username and password.

For codes sent by text message or email, let the agent ask you: it pauses with a question and you reply with `{"secret": "481923"}`. See [Answer a question](/docs/browser-agent/human-in-the-loop#answer-a-question).

## Per-run secrets

When you don't want to store a credential, for example one your own users give you for a single task, pass it in `ephemeral_secrets`. It works like a vault secret, but lives only for the run: it is stored encrypted while the run is live and deleted when it ends.

```json
{
  "goal": "Sign in and tell me the account's plan name.",
  "start_url": "https://app.example.com/login",
  "ephemeral_secrets": {
    "customer_email": { "value": "ada@example.com", "kind": "username", "origins": ["https://app.example.com"] },
    "customer_password": { "value": "s3cret-pa55word", "kind": "password", "origins": ["https://app.example.com"] }
  }
}
```

Names follow the vault rules, and must not repeat a name you also pass in `secrets`. `kind` defaults to `password`, and `origins` is required.

## Credentials in the goal

If the goal itself contains a password ("log in as demo / demo1234"), the agent can type it into the password field. This works, but the goal is sent to the model, returned in the run object and kept in your run history. Use it only for throwaway test accounts. For anything real, use a vault or per-run secret.

## Save and reuse a session

A profile is a saved browser session: the cookies and local storage of the sites a run visited. Save one after a login, and later runs start signed in, with no secrets and no two-factor prompt.

Save a profile with `save_profile` (a name up to 64 characters):

```json
{
  "goal": "Sign in to the shop and tell me my name as shown on the account page.",
  "start_url": "https://shop.example.com/login",
  "secrets": ["shop_user", "shop_password", "shop_totp"],
  "save_profile": "shop-login"
}
```

When the run ends, `profile_saved` in the run object shows what was saved:

```json
{ "profile_saved": { "id": "prof_4c1e9a7b2d0f3e5a8b6c", "name": "shop-login", "cookies": 14 } }
```

Start a later run from it with `profile` (its name or id):

```json
{
  "goal": "Open my orders and tell me the status of the latest one.",
  "start_url": "https://shop.example.com/account/orders",
  "profile": "shop-login"
}
```

- Saving under an existing name updates that profile. To keep a session fresh, pass the same name as both `profile` and `save_profile`.
- The profile is saved whatever the run's final status.
- Sessions expire on the website's schedule. When a profile stops working, run the login again with `save_profile`. Passing the `secrets` along with `profile` lets the agent sign in again on its own if the session has expired.
- An unknown profile returns `400` with `Unknown profile "shop-login" (GET /v1/browser/profiles)`.
- A profile holds live session cookies. Treat it like a password and delete the ones you no longer need.

## Manage secrets and profiles

```bash title="cURL"
# List secrets: names, kinds, origins and dates, never values
curl https://lovelyrouter.com/v1/browser/vault -H "Authorization: Bearer $LOVELY_API_KEY"

# Delete a secret
curl -X DELETE https://lovelyrouter.com/v1/browser/vault/shop_password -H "Authorization: Bearer $LOVELY_API_KEY"

# List profiles
curl https://lovelyrouter.com/v1/browser/profiles -H "Authorization: Bearer $LOVELY_API_KEY"

# Delete a profile by name or id
curl -X DELETE https://lovelyrouter.com/v1/browser/profiles/shop-login -H "Authorization: Bearer $LOVELY_API_KEY"
```

Each secret in the list shows `last_used_at`, so you can see when a run last typed it. The [API reference](/docs/api/browser#list-secrets) has the full response shapes.

## How your secrets are protected

- **Encrypted at rest.** Secrets, per-run secrets, profiles and uploaded files are encrypted with AES-256-GCM, under keys derived separately for your account and for each run. Each value is bound to its owner and name, so it can't be decrypted as anyone else's or under another name.
- **Write-only.** The API never returns a secret value, to you or to anyone with your key.
- **Never sent to a model.** The agent works with names like `shop_password`. The value is decrypted only at the moment it is typed, after the page's site and the field type have been checked.
- **Scrubbed from output.** Any secret value of six characters or more that shows up in page text, the answer, `data` or the step trace is replaced with `[secret:<name>]`. In accurate mode, screenshots stop once a secret has been typed.
- **Short-lived when you want it.** Per-run secrets and secret replies are deleted when the run ends.

Your API key can use the vault, so protect it like the secrets themselves. Limit what a key can do in [API keys](/docs/account/api-keys) and turn on two-factor login for your account in [Account security](/docs/account/security).
