Logins
Keep your users' sign-ins to websites in isolance. The Logins connector holds each of your users' logins, and your workspace's own: their passwords, authenticator codes, passkeys and saved sign-ins. isolance signs in with them inside the browsers it runs for your sandboxes' AI tools (see Browser), and adds a password or an authenticator code to command-line tools' requests on their way out of a sandbox. No secret ever enters a sandbox or reaches an AI, unless a login lets the AI read its password.
Your app manages each user's logins through the API, one user at a time, by your own ID for them, such as user_123: it adds their logins, sends them a link to sign in themselves, and hears when a site signed them out. Your users need no isolance account.
This guide covers the console, the CLI and the API. It assumes you know Connectors and Browser. The CLI examples assume you've set up caas (see The CLI); the API examples send an API key as $CAAS_API_KEY (see The API). The TypeScript SDK has the same as typed methods.
Add Logins
Only owners add it. In the console, open Connectors, choose Add connector, and pick Logins under Pick a tool. It's added at once. A workspace has one Logins connector, which holds every site's logins.
caas connectors add loginsWith the API, POST /v1/connectors with { "catalog": "logins" }. Its ID is logins, unless another connector had that ID first; caas connectors list shows it.
Its Reaches picks the sandboxes that use logins by their labels, as any connector's does (see Reaches); a new Logins connector reaches every sandbox. A sandbox it doesn't reach still has the browser, signed in to nothing. Removing the connector deletes every login, with its passkeys and saved sign-in, and closes every browser of your workspace.
A login
A login is one account on one website, of one of your users or of your workspace:
| Field | What it is |
|---|---|
site | The website, as a host such as github.com, with no https:// or path. It covers the host and every name under it, so github.com covers gist.github.com. Never a domain anyone can register names under, such as co.uk or github.io |
username | The account name, as the site shows it; null for a login that signs in with a passkey alone until the passkey names it |
name | Your own label for it, or null |
methods | How isolance signs in, in order: passkey, typed (isolance types the password; nothing shows it to the AI) or readable (the AI may read the password too). ["passkey", "typed"] unless you say; [] signs in by hand only. typed and readable don't go together |
browser | Which browser signs in to it: native, isolance's own browsers (the default), or browserbase, your Browserbase account (see Run on your Browserbase account) |
signInUrl | Where signing in starts, an https:// address on the site. Without it, https://<site>/ |
check | { "url", "text" }: a page and a text on it that show only when signed in, such as { "url": "https://github.com/settings/profile", "text": "Public profile" }. Without it, a page that asks for no account name, password, code or passkey counts as signed in |
password | The password. Sent, never shown back |
code | The authenticator setup key: the text under the QR code the site shows when you turn on an authenticator app, or the otpauth://totp/… link in it. Sent, never shown back |
passkeys | { "signer", "creators" }: who signs and who may make one (see Passkeys), and the passkeys themselves |
deliveries | Where command-line tools get its password or code (see Command-line tools) |
Whose login a sandbox uses. A sandbox started for a user uses that user's logins; a sandbox started without a user uses your workspace's. Never another user's, and never your workspace's for a user, as for every connector (see Whose account a sandbox uses).
A user's logins are under /v1/users/<user>/logins; your workspace's under /v1/connectors/logins/account/logins, with the same routes and fields.
Add a login for a user
From your app, with an API key that has sandbox:write:
curl https://api.caas.sh/v1/users/user_123/logins \
-H "Authorization: Bearer $CAAS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "site": "github.com", "username": "ana@acme.com", "methods": ["passkey", "typed"],
"password": "…", "code": "otpauth://totp/GitHub:ana?secret=JBSWY3DPEHPK3PXP&issuer=GitHub" }'The answer, 201, is the login, which never carries the password or the code:
{ "id": "lg_4f2a91c03b7d6e15", "site": "github.com", "username": "ana@acme.com", "name": null,
"methods": ["passkey", "typed"], "browser": "native", "signInUrl": null, "check": null,
"password": { "savedAt": 1792800000000, "savedBy": "api key backend" },
"code": { "addedAt": 1792800000000, "digits": 6, "periodSeconds": 30 },
"passkeys": { "signer": "caas", "creators": ["user", "platform", "agent"], "items": [] },
"session": null, "deliveries": [], "live": null,
"createdAt": 1792800000000, "createdBy": "api key backend", "updatedAt": 1792800000000, "lastUsedAt": null }A user has one login per site and account name: a second is 409. Don't have the password? Add the login without it and send your user a link, whose page signs them in.
In the console, a user's page (Connectors, then Users) has a Logins card: each login's site, account, how it signs in, which browser, its passkeys and saved sign-in, with Edit, Send a link, Sign in by hand, Check now and Remove in its menu, and Add login. Your workspace's logins are on the Logins connector's page, under The workspace's logins, where owners add them the same way.
With the CLI, secrets come only from standard input, never as options: one per command, or the password then the setup key on the next line.
caas logins add --user user_123 --site github.com --username ana@acme.com --methods passkey,typed --password-stdin < password.txt
caas logins list --user user_123How a login signs in
When the AI calls login_sign_in, isolance opens the site's sign-in page and checks whether it's already signed in, then tries the login's methods in order until the page is signed in:
passkey, when the login has passkeys: isolance answers the passkey request the page makes, or presses its passkey button and answers that.typed: isolance types the account name and password into the page and submits it, then the current authenticator code when the site asks for one. The AI never sees either.readable: the same, and the AI may also read the password and the current code (login_password,login_code), such as to type them in a terminal or an app. Choose it only where the AI needs them: isolance then keeps none of that login's secrets from it.
The first that works ends it, and the sign-in is saved for the next browser. When none works, the AI gets the page as it stands, and can type the fields the site asks for with login_fill or ask a person (see When a sandbox asks for a person). A login with methods: [] is signed in only by a person, and kept as a saved sign-in.
isolance types and answers only into pages none of the AI's scripts, routes or storage changes has touched, and saves no sign-in of a site they reached from that browser (see Signing in).
Passwords and authenticator codes
A password or setup key is kept in isolance's credential store and never shown again: a login shows only when it was saved and by whom, such as "Saved Oct 24 by owner@acme.com". An otpauth://totp/ link gives its code's digits, period and algorithm; a bare setup key gets the usual 6 digits every 30 seconds.
Replace one by sending it again with
PATCH /v1/users/<user>/logins/<login>, and remove it with"password": nullor"code": null.A change of the account name, the password or the setup key replaces all three at once, so send the ones the login keeps along with it; isolance keeps no copy to fill them in from (
400names the one missing). Changing only the password of a login that also has a setup key isn't possible yet.A change applies from the next sign-in. A sign-in under way finishes with what it read, and the saved sign-in it made stays.
Nothing shows a code: isolance types it where a site asks for one, adds it to a command-line tool's request, or gives it to the AI only for a
readablelogin.
Passkeys
A login can have several passkeys, listed under it with their ID, their credential ID as the site lists it (so you can match the site's own list), their name, when and how each was made, when it was last used, and how many times.
Making one. A passkey is made only on the site's own page, in a browser isolance runs, when the site offers to add one: by the AI in its browser, by your user in their link's live view, or by your team or your app in a live view.
passkeys.creatorssays who may: any ofagent,userandplatform, all three unless you say. A site with no login gets no passkey: add a login for it first (with"methods": ["passkey"]for a passkey alone). When a person finishes a sandbox's sign-in in a live view, whoever opens it may add one if the login's creators includeuserorplatform.Who signs (
passkeys.signer). Withcaas, the default, isolance answers each of the site's passkey requests itself, and the passkey's key never leaves isolance's credential store. Withbrowser, isolance puts the key in the browser for the length of its sign-in step and takes it out after, for a site that doesn't work with the first way.Only the real site. isolance answers a passkey request only from the login's site or a name under it, or from another site the login's site itself lists as its own, and only in a page none of the AI's scripts or routes has touched. A look-alike site gets nothing.
What a site sees. Every sign-in says the person was verified: your app's request that started the sandbox or the link is that verification. isolance's passkeys are kept in isolance, not on a device, as synced passkeys are. A site that refuses them, such as one that takes only passkeys bound to a device, may work with signer
browser.Rename one with
PATCH …/logins/<login>/passkeys/<passkey>and{ "name" }. Remove one withDELETE …/passkeys/<passkey>: isolance stops using it from the site's next request, but the site still lists it until you remove it there too, which the answer says.
Saved sign-ins
A login's saved sign-in is the site's cookies and storage after a sign-in, so the next browser starts signed in and doesn't sign in again.
Saved after a sign-in, at
login_save, when a browser closes, and when a person chooses Done in a live view.Uploaded from your own browser automation:
PUT …/logins/<login>/sessionwith{ "state": … }, in Playwright's storage state form, asawait context.storageState()gives it. Cookies of other sites are dropped, and the answer counts them indropped. Up to 1 MiB.Status:
saved,signed_out(a site signed it out, or a person marked it so) orexpired(its last cookie ran out). The next sign-in replaces it.Removed with
DELETE …/session, which marks it signed out.Two browsers at once. Two sandboxes of a user that use the same login each start from the same saved sign-in; the later save wins.
On Browserbase, a user's saved sign-ins live in their Browserbase profile, shared by all their Browserbase browsers and saved when each closes. Nothing can be uploaded into it (409): sign in by hand or send a link instead. Removing a Browserbase login's saved sign-in, or the login itself, closes the user's Browserbase browsers and starts their next one on a new profile, signed out of every site there, as Browserbase keeps one sign-in for all of them; the answer says so. Replacing or removing your Browserbase key, or removing the Browserbase connector, does the same for every user, since a new key may be another Browserbase project's.
Removing a login closes every browser using it at once, so no AI stays signed in with it.
Send your user a link
A login's link lets your user sign in to the site themselves, on a page isolance hosts: your app never handles their password.
curl https://api.caas.sh/v1/users/user_123/logins/link \
-H "Authorization: Bearer $CAAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "site": "github.com", "returnUrl": "https://app.acme.com/settings" }'POST /v1/users/<user>/logins/link with { "site" } makes the login, or takes the user's one login there; POST /v1/users/<user>/logins/<login>/link makes a link for a login you have. The answer, 201, is { "login", "connectUrl" }. Send your user to connectUrl:
Sign in to github.com opens a live view: a browser isolance runs, at the site's sign-in page, which your user drives. They sign in, add a passkey if the site offers one and the login's creators include
user, and choose Done. A live view of isolance's own browsers opens in a new tab; Browserbase's shows in the page.Or, under Or enter your password, they type their account name, password, and an authenticator setup key if they have one.
The page says "github.com is connected" and sends them to your
returnUrlwithloginandstatusadded:?login=lg_4f2a91c03b7d6e15&status=connected.
A link works until it's used or a newer link for the same login replaces it, in any browser. If a sandbox is waiting for a person on that login, the link's page shows that sandbox's browser (see When a sandbox asks for a person).
caas logins link --site github.com --user user_123 --return-url https://app.acme.com/settingsIn the console, choose Send a link in the login's menu on the user's page.
Sign in by hand
A live view is a browser isolance opens at a login's sign-in page for a person to drive: someone on your team, or your app on your user's behalf.
POST …/logins/<login>/liveopens it and answers201with{ "live" }: itsurl,expiresAtandembeddable. The person signs in, and adds a passkey if the login's creators includeplatform.POST …/live/donesaves the sign-in when the site is signed in, and closes the browser, answering the login, whoselivesays what it found:signedIn,saved(on Browserbase the profile keeps the sign-in when the browser closes, sofalsehere) andreason, why nothing was saved when isolance's browser wouldn't.DELETE …/liveends it without saving. A login has one live view at a time (409).A view's address can be opened for up to an hour on isolance's browsers, or six hours on Browserbase; one nobody finishes then closes, and its browser time counts until it would have idled out (20 minutes after it opened, on isolance's browsers).
In the console, Sign in by hand in the login's menu opens it, with Done and Cancel. With the CLI,
caas logins live LOGIN --user USERprints its address, then saves the sign-in when you press Enter, or cancels at Ctrl-C.
Guard the address. Whoever has a live view's url drives that browser: they can go anywhere in it and see every page, until Done, a cancel or the browser's end, and its time bills to you. isolance shows it only in the answer that opened it and in single reads of the login by owners, operators and API keys with sandbox:write (for your workspace's logins, owners only), never in lists, to viewers or in logs. If your app passes it on, give it only to the person who is to sign in, on a page of your own they're signed in to, never log it, and end the view when they leave. For your users, prefer their link: isolance hosts the page, it serves only that login, and your app never sees the address. A view with embeddable: true (Browserbase's) can be shown inside your own page; one of isolance's own browsers opens in a tab of its own.
When a sandbox asks for a person
When an AI calls login_handoff, such as for a site that sends a code to a phone, the login's live becomes { "status": "waiting", "from": { "sandbox" }, "message", "url", "expiresAt" }. The person sees the page the AI left. Your app then shows the person the live view, or sends your user the login's link, whose page shows it. While the view is open, the AI's browser tools in that browser wait, and the browser doesn't close for being idle; when the person chooses Done, the sign-in is saved when the site is signed in, and the AI goes on, reading where it stands with login_status. A site the AI's scripts ran on in that browser keeps no sign-in from it, so there Done saves nothing and the login's live.reason says why. A view nobody opens lapses (ended). The AI's message to the person is up to 256 characters. Only a sandbox the Logins connector picks can ask, as only those use logins.
A sandbox without a user asks for a person on your workspace's login: its address goes only to owners, in the console or a read of the login, never into the sandbox. The sandbox only ever sees waiting, then done or ended.
When a site signs a user out
isolance sees that a site signed a saved sign-in out when it next uses the login: a sign-in or a check finds it signed out, or the site expires its sign-in cookies while a browser runs. It marks the saved sign-in signed_out, and the user's page shows Signed out.
A site that signs someone out on its own servers, such as after a password reset elsewhere or a device removed, while no browser uses the login, tells nobody: isolance sees it only at the login's next use. To know sooner, check:
curl -X POST https://api.caas.sh/v1/users/user_123/logins/lg_4f2a91c03b7d6e15/check \
-H "Authorization: Bearer $CAAS_API_KEY"A check opens a browser for the login, puts its saved sign-in back, sees whether the site is signed in, and closes, answering { "signedIn", "checkedAt", "session" }. It bills as any browser. isolance never checks on its own: how often is yours, from your own scheduler. In the console, Check now in the login's menu; with the CLI, caas logins check LOGIN --user USER.
Command-line tools
A login's deliveries add its password, account name or current authenticator code to a header of the HTTPS requests a sandbox sends to a host, on their way out, as a connector's key is added:
{ "deliveries": [{ "host": "registry.npmjs.org", "header": "npm-otp", "value": "code" }] }With that on a user's npm login, npm publish in their sandbox sends the code npm asks for without the sandbox ever holding the setup key. Each delivery is { "host", "header", "value", "scheme" }:
valueispassword,usernameorcode(which needs a setup key).schemeisraw(the value as it is, the default),bearer(Bearer <value>) orbasic(the account name and password as HTTP Basic authentication). A code goesraw.hostcan't be one of isolance's or one another connector covers, and the header follows a connector's rules (see Add an API).
A sandbox started for a user gets that user's logins' deliveries, one without a user your workspace's. They count toward the sandbox's 16 egress rules and 32 connector hosts, with its connectors: a login that doesn't fit beside those before it is left out whole, and the sandbox's log names it. Like connectors, a change reaches a running sandbox at its next start.
Who can do what
| What | Who |
|---|---|
| See logins, their passkeys and saved sign-ins | Everyone in the workspace, and API keys with sandbox:read |
| Add, change and remove a user's logins, send links, open live views and see their addresses, and check | Owners and operators, and API keys with sandbox:write |
| The same for your workspace's logins | Owners, asked afresh |
| Use a login | The sandboxes of its user (your workspace's: sandboxes without a user) that the Logins connector reaches |
While a workspace is suspended, changes are refused. The audit log records every use of a login's secrets, every passkey signing included: login.create, login.update, login.delete, login.link, login.check, login.live.open, login.live.done, login.live.cancel, login.session.save, login.session.delete, login.sign_in, login.fill, login.read (the AI read a readable login's password or code), login.handoff, login.session.end (a site signed a saved sign-in out), passkey.create, passkey.sign, passkey.update and passkey.delete. What a sandbox's AI did carries the sandbox and its user as who did it, never the person or key that started the sandbox; what a user did through their link carries that user. No record carries a password, code, key, cookie or live view address.
caas audit --action login
caas audit --action passkeyThe audit log keeps each record 365 days, and its newest 1,000,000. To keep every record longer, read it into your own store with GET /v1/audit-log (see Settings and the audit log).
Limits
| Limit | Value |
|---|---|
| Logins connectors | 1 per workspace |
| Logins | One per owner, site and account name |
| A site | 253 characters |
| A password or setup key | 16 KiB |
| Deliveries | 16 per login, within a sandbox's 16 egress rules and 32 connector hosts with its connectors |
| A saved sign-in | 1 MiB, once kept for its site |
| A live view's address before it's opened | 1 hour on isolance's browsers, 6 hours on Browserbase |
| Live views | 1 at a time per login |
| Browserbase logins of a site | 1 per user, and 1 for your workspace |
| Lists | ?limit= 1 to 100, 50 by default |
Use the API
Every path below is under https://api.caas.sh/v1, where {base} is /users/<user>/logins for a user's logins, or /connectors/<id>/account/logins for your workspace's (<id> the Logins connector's). Errors are as for every route (see The API); without the Logins connector, every route answers 404.
| Request | Who | Does |
|---|---|---|
GET {base} | sandbox:read | { logins, nextCursor }, by site, then account name. ?site= for the logins that cover a site; ?limit= and ?cursor= |
POST {base} | sandbox:write | Adds a login (see A login); an Idempotency-Key adds it once |
GET {base}/<login> | sandbox:read | The login, with its live view's address to who may see it |
PATCH {base}/<login> | sandbox:write | Changes any field but site; "password": null and "code": null remove them |
DELETE {base}/<login> | sandbox:write | Removes it, its passkeys and its saved sign-in |
PATCH, DELETE {base}/<login>/passkeys/<passkey> | sandbox:write | Renames a passkey, { "name" }, or removes it |
GET, PUT, DELETE {base}/<login>/session | sandbox:read, sandbox:write | Reads, uploads ({ "state" }) or removes its saved sign-in |
POST {base}/<login>/live, POST …/live/done, DELETE …/live | sandbox:write | Opens a live view, finishes it with the sign-in saved, or ends it unsaved |
POST {base}/<login>/check | sandbox:write | Checks whether the site is still signed in: { signedIn, checkedAt, session } |
POST /users/<user>/logins/<login>/link, POST /users/<user>/logins/link | sandbox:write | A link for your user: { "returnUrl"? }, or { "site", "returnUrl"? }, answering { login, connectUrl } |
For your workspace's logins, the changes need an owner. Refusals name the field at fault (400), such as a site that's a public suffix, typed with readable, "browser": "browserbase" without the Browserbase connector, or a delivery's host another connector covers; 409 is a live view already open, a second login of the same site and account name, or a second Browserbase login of a site, which names the way out.