---
title: "Credentials & logins"
description: "Store a login against a profile so an agent can sign in without ever holding the password: one account per service, two-factor codes included, and the vault password that guards reading one back."
canonical: "https://scalebrowser.net/docs/credentials"
---

> ## Documentation Index
> Fetch the complete documentation index at: https://scalebrowser.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Credentials & logins

> Store a login against a profile so an agent can sign in without ever holding the password: one account per service, two-factor codes included, and the vault password that guards reading one back.

An agent asked to sign into an account otherwise carries the password in its own context, which means the model transcript, the framework's log and whatever the orchestrator persists. Storing the login against the profile ends that: the agent names the fields, the daemon types the values.

## One account per service, per profile
The key is `(profile, platform)`, and `platform` is the service's **registrable domain**. `amazon.de` and `amazon.co.uk` are two platforms; `www.amazon.de` and `amazon.de` are one.

That is derived rather than chosen, using Mozilla's public suffix list, and it matters more than it sounds. When the key was a hand-picked slug, agents wrote `outlook.com` while the interface offered `outlook`, so one login became two rows and a fill found whichever half it asked for. A derived key cannot be argued about.

<Note>

**Two accounts on one service means two profiles.** That is the safer arrangement anyway, and it is what lets a fill take only the service name and never pick the wrong account.

</Note>

## What is stored
| Part            | Notes                                                                             |
| --------------- | ----------------------------------------------------------------------------------- |
| Username        | Not a secret on any platform, and without it you cannot tell two stored accounts apart. |
| Password        | Encrypted at rest under the daemon's master key.                                   |
| 2FA setup key   | The TOTP seed. The daemon computes the six digits when a fill needs them.           |
| Sign-in address | Where the login belongs.                                                            |
| Binding         | Where this login may be typed at all. See [Secrets](/docs/secrets).                 |

A listing gives you the metadata and never a value: `has_password`, `has_totp`, `username`, `last_used_at`.

Two flags are worth knowing:

- **`pending`** means the password was written before it was typed into a sign-up form and the sign-up was never confirmed. That order is deliberate: a crash mid-registration must not lose a password the platform has already seen.
- **`secrets_unreadable`** means the stored blobs would not decrypt with the current master key. The row stays listable and deletable, and every use of it fails closed.

## Managing them
| Call                                                     | What it does                                                      |
| -------------------------------------------------------- | ------------------------------------------------------------------- |
| `GET /v1/profiles/:id/credentials`                       | Metadata for every stored login on this profile.                   |
| `PUT /v1/profiles/:id/credentials/:platform`             | Store or replace one.                                              |
| `DELETE /v1/profiles/:id/credentials/:platform`          | Remove it.                                                          |
| `POST /v1/profiles/:id/credentials/:platform/reveal`     | Read the values back. Needs the vault password.                    |
| `POST /v1/profiles/:id/credential-bundle/export`         | All of this profile's logins, sealed with a passphrase you choose. |
| `POST /v1/profiles/:id/credential-bundle/import`         | Unseal one into a profile.                                          |

## The vault password
Revealing a stored value needs a second secret, and it is not the API token.

The reason is structural: MCP and the REST surface sit behind the **same** bearer token, and that token is on the agent's side of the wall. Without a second gate, any agent that can call the API could read back the very password the whole feature exists to keep away from it.

```bash
$ curl http://127.0.0.1:8787/v1/vault \
    -H "Authorization: Bearer $SCALEBROWSER_BEARER_TOKEN"
```

`GET /v1/vault` says whether a password is set and is deliberately unguarded: the answer is not a secret, and a client has to know whether to prompt. `PUT /v1/vault` sets or changes it.

<Warning>

**A wrong password, an empty one and an unconfigured vault are indistinguishable.** All three refuse the same way. An unset vault is a closed one, and the caller learns nothing about the state of the machine from a failed attempt.

</Warning>

The vault password is deliberately not the sync passphrase. Hanging it on that one would leave the vault locked after every daemon restart, which is exactly when an unattended fleet needs it.

## What an agent gets
Three tools, all in the `credentials` set. **The desktop app serves that set**, because without it there is no tool that produces a password at all, and an agent asked to sign up can then only think one up and type it in the clear. On a daemon you run yourself the set is opt-in and off by default; switch it on with `profiles = ["core", "credentials"]`.

<Note>

**An agent should never invent a password.** If one is typed into a password field as plain text, the reply says so and names these tools. Nothing is blocked: the field is filled and the run continues.

</Note>

**`credential_fill`** signs in with the stored login. The agent names the username, password and one-time-code fields from a snapshot; the daemon types the values. It never receives them.

Every value is resolved **before the first keystroke**. A missing one leaves the form untouched rather than half filled, which is also why every secret refusal is a `precondition` failure.

**`credential_new`** signs up. The daemon draws a password, stores it against the profile, and types it into every password field named, so a first and repeat field get the same value. The agent gets back the length and never the value. Pass a policy when the form states its own rules.

**`credential_store`** adopts an account the profile already has. Send only the parts you have; the rest keeps its stored value. Nothing is typed and no page is touched.

<Warning>

**`credential_store` is the only tool in the whole catalog whose argument carries a secret.** That is deliberate rather than an oversight: a login the agent is being asked to store is already in its context, and storing it is what stops it having to travel again.

Because there is one login per service per profile, a store **replaces** what is there.

</Warning>

Two more tools sit in the same set for the same reason: `read_inbox` and `inbox_open_link`, because a confirmation code is the account's own secret and it is what ends nearly every sign-up. See [Email inboxes](/docs/inboxes).

## Two-factor
Store the 2FA setup key, the string behind the QR code, and the daemon computes the six digits itself when a fill needs one. The agent names the code field; it never sees the seed and never sees the digits.

That is the whole feature. There is no second device, no push, and no support for a factor that is not a time-based code.

## Next
- [Secrets](/docs/secrets): the layer that keeps values out of a transcript in general.
- [Email inboxes](/docs/inboxes): the confirmation code half of a sign-up.
- [Passkeys](/docs/passkeys): the credential that has no password at all.
- [Cookies & sessions](/docs/sessions): staying signed in once you are.
