A profile’s data REST API

Credentials

Logins saved per profile, with their passwords and two-factor secrets encrypted through the OS keychain. No endpoint returns a secret; the only way one leaves the app is as keystrokes into the browser.

List a profile’s saved logins

GET/api/v1/profiles/:id/credentials

Returns summaries: label, site, username, and whether a password and a two-factor secret exist — never the values. encryptionAvailable reports whether this machine has a usable keychain; when it is false, secrets are stored as plain text and each record says so in its own encrypted field. Logins are held per profile rather than in one shared vault, because a shared vault makes it possible to fill the wrong account into the wrong session.

Path parameters

FieldTypeWhat it is
idrequiredstringThe record’s id.

Returns

{ credentials: CredentialSummary[], encryptionAvailable: boolean }

Save a login

POST/api/v1/profiles/:id/credentials

Encrypts the password and any two-factor secret before writing them. totpSecret is accepted as base32 in any spacing or case, or as a whole otpauth:// URI copied from behind a QR code — all three are the same secret and all three work, so a user cannot silently save a broken credential and find out at the login screen.

Path parameters

FieldTypeWhat it is
idrequiredstringThe record’s id.

Body

FieldTypeWhat it is
labelstringWhat this login is for. Defaults to the site.
sitestringThe site it belongs to.
usernamestringUsername or email address.
passwordstringPlain text in, ciphertext at rest.
totpSecretstringbase32 or an otpauth:// URI. A key that cannot decode, or that decodes to under 64 bits, is refused by name rather than stored — a truncated paste otherwise produces six wrong digits for ever.
notesstringAnything else worth remembering.

Returns

A CredentialSummary.

Change a saved login

PATCH/api/v1/profiles/:id/credentials/:credentialId

Only the fields you send change. Omitting password leaves the stored one alone — that is the point of a patch here, because the editor shows a placeholder rather than the real value and an untouched form must not wipe the password with the empty string it appears to contain. Sending totpSecret as an empty string removes two-factor from the login.

Path parameters

FieldTypeWhat it is
idrequiredstringThe record’s id.
credentialIdstringThe credential to change.

Body

FieldTypeWhat it is
labelstringWhat this login is for.
sitestringThe site it belongs to.
usernamestringUsername or email address.
passwordstringOmit to keep the current one.
totpSecretstringEmpty string removes it.
notesstringAnything else worth remembering.

Returns

The updated CredentialSummary.

Errors

  • 404 — no credential with that id on this profile.

Delete a saved login

DELETE/api/v1/profiles/:id/credentials/:credentialId

Removes it immediately. Credentials do not go to the trash — a password you asked to delete should be gone.

Path parameters

FieldTypeWhat it is
idrequiredstringThe record’s id.
credentialIdstringThe credential to delete.

Returns

{ removed: true }

Errors

  • 404 — no credential with that id on this profile.

Read the browser’s own saved logins

GET/api/v1/profiles/:id/browser/logins

What Chromium itself saved while somebody used this profile, which is a different set from /credentials — that one is what Mosaic holds for the profile and fills on demand. Passwords are decrypted with the profile’s own key; a row that could not be decrypted comes back with password: null, which is not the same as an empty password and is never silently turned into one.

passwordManagerOff says the profile has Chromium’s password manager disabled, so an empty list there is a setting rather than a fact about the browser.

Path parameters

FieldTypeWhat it is
idrequiredstringThe record’s id.

Returns

{ logins: [{ realm, url, username, password, createdAt, timesUsed }], passwordManagerOff }

Errors

  • 404 — no profile with that id.

Save a login into the browser itself

POST/api/v1/profiles/:id/browser/logins

Writes a row Chromium will offer to fill, as though the browser had saved it. The profile has to be stopped: Chromium keeps its password store in memory while it runs and rewrites the file on exit, so a login added underneath a live browser is discarded without a word when that browser closes — which is why this refuses rather than returning a 200 that did nothing.

Path parameters

FieldTypeWhat it is
idrequiredstringThe record’s id.

Body

FieldTypeWhat it is
urlrequiredstringThe sign-in page. Its origin becomes the realm.
usernamerequiredstringOr the email address the site signs in with.
passwordrequiredstringPlain text. Chromium encrypts it with this profile’s own key.

Returns

{ logins } — the browser’s list, re-read after the write.

Errors

  • 400 — the profile is running, or a field is missing.
  • 404 — no profile with that id.
curl -X POST -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://example.com/login","username":"eugene","password":"…"}' \
  "$BASE/profiles/$ID/browser/logins"

Remove one saved login from the browser

DELETE/api/v1/profiles/:id/browser/logins

Scoped by realm and username, both required. An absent scope is a 400 and never "every login this browser has": on an irreversible route silence is refused rather than read as consent. Needs the profile stopped, for the same reason adding one does.

Path parameters

FieldTypeWhat it is
idrequiredstringThe record’s id.

Body

FieldTypeWhat it is
realmrequiredstringAs GET reports it.
usernamerequiredstringThe row’s username, exactly as reported.

Returns

{ removed, logins }

Errors

  • 400 — the profile is running, or a field is missing.
  • 404 — no profile with that id, or no login matching that realm and username.

Read the browser’s own bookmarks

GET/api/v1/profiles/:id/browser/bookmarks

Chromium’s bookmark tree, flattened, with the folder each one sits in. This is what the profile has accumulated by being used — distinct from the bookmarks field on the profile, which is what Mosaic writes into the bar before every launch. A profile that has never run has no bookmarks file and answers with an empty list rather than an error.

Path parameters

FieldTypeWhat it is
idrequiredstringThe record’s id.

Returns

{ bookmarks: [{ title, url, folder }] }

Errors

  • 404 — no profile with that id.

Read the domains this profile has visited

GET/api/v1/profiles/:id/browser/history

Domains and visit counts, most visited first — not individual URLs. It is what the editor offers as suggestions when saving a login, and it is enough to answer "has this profile actually been used" without reading somebody’s browsing.

Path parameters

FieldTypeWhat it is
idrequiredstringThe record’s id.

Query parameters

FieldTypeWhat it is
limitnumberHow many domains. Default 40. A non-numeric value is a 400 naming the field.

Returns

{ domains: [{ domain, visits }] }

Errors

  • 400 — limit is not a whole number.
  • 404 — no profile with that id.

Generate a two-factor code

GET/api/v1/profiles/:id/credentials/:credentialId/totp

Returns the current RFC 6238 code and how long it stays valid. Check expiresIn before using it: a code with a second left will be rejected between generating it and submitting the form, which is the usual reason a working authenticator looks broken.

With ?reveal=1 the reply also carries the setup key itself and an otpauth:// URI for it. That is deliberate: a two-factor secret you cannot read back is one you cannot move to a phone or to another tool, and a profile is meant to carry everything an identity needs. The key is left out of the plain reply because the companion polls this route once a second while its panel is open, and a secret has no business in that traffic.

Path parameters

FieldTypeWhat it is
idrequiredstringThe record’s id.
credentialIdstringThe credential to generate for.

Query parameters

FieldTypeWhat it is
revealstring1 also returns secret and otpauth.

Returns

{ code, expiresIn, period }, plus { secret, otpauth } when reveal=1

Errors

  • 404 — no credential with that id on this profile.
  • 400 — this login has no two-factor secret, or it could not be decrypted on this machine.

Type a login into the running profile

POST/api/v1/profiles/:id/credentials/:credentialId/fill

Types one field into whatever is focused in the running browser, as real keystrokes with human timing — not insertText and not by setting .value, because a field that goes from empty to twenty characters in one event fires no keydown and a login form that receives no keystrokes is a cheap bot signal. Focus the field in the browser first: this deliberately does not guess which input is the password box, since guessing wrong types a password into a search field. Asking for totp when the current code is nearly expired waits for the next one rather than filling a dead code.

Path parameters

FieldTypeWhat it is
idrequiredstringThe record’s id.
credentialIdstringThe credential to fill from.

Body

FieldTypeWhat it is
fieldstringusername, password or totp. Default password.

Returns

{ field, length, expiresIn? } — a character count, never the value.

Errors

  • 400 — field is not one of the three.
  • 400 — the profile is not running, or no tab has a focused field.
  • 404 — no credential with that id on this profile.
curl -X POST -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' \
  -d '{"field":"totp"}' \
  "$BASE/profiles/$ID/credentials/$CID/fill"
Next Try it on your own accounts

Run the API on your own profiles

The REST API and the MCP server are part of Pro: $49 a month, with the first 14 days free. Free has every browser feature and no API key.