ScalebrowserDOCS

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.

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.

What is stored

PartNotes
UsernameNot a secret on any platform, and without it you cannot tell two stored accounts apart.
PasswordEncrypted at rest under the daemon's master key.
2FA setup keyThe TOTP seed. The daemon computes the six digits when a fill needs them.
Sign-in addressWhere the login belongs.
BindingWhere this login may be typed at all. See 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

CallWhat it does
GET /v1/profiles/:id/credentialsMetadata for every stored login on this profile.
PUT /v1/profiles/:id/credentials/:platformStore or replace one.
DELETE /v1/profiles/:id/credentials/:platformRemove it.
POST /v1/profiles/:id/credentials/:platform/revealRead the values back. Needs the vault password.
POST /v1/profiles/:id/credential-bundle/exportAll of this profile's logins, sealed with a passphrase you choose.
POST /v1/profiles/:id/credential-bundle/importUnseal 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.

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"].

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.

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.

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.

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: the layer that keeps values out of a transcript in general.
  • Email inboxes: the confirmation code half of a sign-up.
  • Passkeys: the credential that has no password at all.
  • Cookies & sessions: staying signed in once you are.