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.
- GETList a profile’s saved logins
- POSTSave a login
- PATCHChange a saved login
- DELETEDelete a saved login
- GETRead the browser’s own saved logins
- POSTSave a login into the browser itself
- DELETERemove one saved login from the browser
- GETRead the browser’s own bookmarks
- GETRead the domains this profile has visited
- GETGenerate a two-factor code
- POSTType a login into the running profile
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
| Field | Type | What it is |
|---|---|---|
idrequired | string | The 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
| Field | Type | What it is |
|---|---|---|
idrequired | string | The record’s id. |
Body
| Field | Type | What it is |
|---|---|---|
label | string | What this login is for. Defaults to the site. |
site | string | The site it belongs to. |
username | string | Username or email address. |
password | string | Plain text in, ciphertext at rest. |
totpSecret | string | base32 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. |
notes | string | Anything 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
| Field | Type | What it is |
|---|---|---|
idrequired | string | The record’s id. |
credentialId | string | The credential to change. |
Body
| Field | Type | What it is |
|---|---|---|
label | string | What this login is for. |
site | string | The site it belongs to. |
username | string | Username or email address. |
password | string | Omit to keep the current one. |
totpSecret | string | Empty string removes it. |
notes | string | Anything 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
| Field | Type | What it is |
|---|---|---|
idrequired | string | The record’s id. |
credentialId | string | The 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
| Field | Type | What it is |
|---|---|---|
idrequired | string | The 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
| Field | Type | What it is |
|---|---|---|
idrequired | string | The record’s id. |
Body
| Field | Type | What it is |
|---|---|---|
urlrequired | string | The sign-in page. Its origin becomes the realm. |
usernamerequired | string | Or the email address the site signs in with. |
passwordrequired | string | Plain 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
| Field | Type | What it is |
|---|---|---|
idrequired | string | The record’s id. |
Body
| Field | Type | What it is |
|---|---|---|
realmrequired | string | As GET reports it. |
usernamerequired | string | The 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
| Field | Type | What it is |
|---|---|---|
idrequired | string | The 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
| Field | Type | What it is |
|---|---|---|
idrequired | string | The record’s id. |
Query parameters
| Field | Type | What it is |
|---|---|---|
limit | number | How many domains. Default 40. A non-numeric value is a 400 naming the field. |
Returns
{ domains: [{ domain, visits }] }
Errors
- 400 —
limitis 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
| Field | Type | What it is |
|---|---|---|
idrequired | string | The record’s id. |
credentialId | string | The credential to generate for. |
Query parameters
| Field | Type | What it is |
|---|---|---|
reveal | string | 1 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
| Field | Type | What it is |
|---|---|---|
idrequired | string | The record’s id. |
credentialId | string | The credential to fill from. |
Body
| Field | Type | What it is |
|---|---|---|
field | string | username, password or totp. Default password. |
Returns
{ field, length, expiresIn? } — a character count, never the value.
Errors
- 400 —
fieldis 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"
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.
Mosaic is a desktop app
Email yourself the link and open it on your computer.