Accounts, subaccounts and CLI profiles¶
Editions: OSS, Cloud, Enterprise. Unless stated otherwise, everything on this page ships in OSS.
Multi-account, subaccounts, sharing and access rules are served by an
extension plugin. The open-source server does not have these endpoints. The
console and the CLI show the related views and commands only when
GET /api/v1/features reports the matching capability:
| Capability | Console | CLI |
|---|---|---|
multi_account |
Header account switcher (with "New account…"), account chooser at sign-in | accounts works against the server's memberships |
account_hierarchy |
Subaccounts card on Settings > Account, "Subaccount access" on Users and Teams, Settings > Access grants, sharing on resource pages, read-only shared views, usage and attention by subaccount | subaccounts, share |
abac_rules |
Tags on resource pages, Policies > Access rules | tags, access |
All three are false unless a plugin sets them. With all three off, none of
the gated routes, nav items or panels is registered and their code is never
downloaded. /console/settings/subaccounts still renders the subaccounts
page for old links, but no nav item points at it. If an account endpoint answers 404 anyway (for example after the
plugin was removed), the Subaccounts and Access grants pages say the feature
is not available on this deployment and other gated panels hide, without an
error toast. A 404 on one item, such as a sibling's subaccount id, shows "not
found".
CLI profiles and accounts¶
~/.preloop/config.yaml can hold several named profiles, and each profile
can hold one token pair per account. A file written before profiles existed
keeps working unchanged: its top-level keys are the default profile.
# The default profile, exactly as before profiles existed.
access_token: <token>
refresh_token: <token>
api_url: https://preloop.ai
current_profile: work # optional; used when no flag or env var picks one
profiles:
work:
api_url: https://preloop.example.com
access_token: <login token>
refresh_token: <login token>
current_account: north # set by `preloop accounts switch`
accounts:
north:
account_id: 6c1f...
name: North site
access_token: <token for north>
refresh_token: <token for north>
Profile and account names are lowercase letters, digits, - and _.
Which session a command uses¶
- Profile:
--profile, thenPRELOOP_PROFILE, thencurrent_profile, thendefault. - Account:
--account, thenPRELOOP_ACCOUNT, then the profile'scurrent_account. With no account, the profile's own token pair is used. - Token:
--tokenandPRELOOP_TOKENstill override everything.
If an account is asked for and the profile holds no tokens for it, the
command stops with a hint to run preloop accounts switch <slug>. It never
falls back to another account's tokens.
Commands¶
preloop accounts listlists the accounts you belong to and marks the current one.preloop accounts switch <slug|id>asks the server for a token pair for that account, stores both tokens under the profile and makes it current.preloop accounts currentprints the profile and account in use;preloop auth statusshows them too.preloop subaccounts list|create|rename|detach|delete(account_hierarchy).preloop share list|add|rm(account_hierarchy).share addadds a share and leaves existing shares alone;share rm <share-id>stops one.preloop tags list|set|rm <kind> <id>(abac_rules). Keys the parent account governs are read-only.preloop access rules list,preloop access rules apply -f <file|->andpreloop access explain --subject --action --resource(abac_rules).applyreplaces this account's rules with the file, so the file is the source of truth.
The gated groups are hidden from preloop --help unless the server reports
their capability. Running one anyway prints which capability is off.
Sign out everywhere¶
In Settings → Security, choose Sign out everywhere to end your
browser and CLI login sessions across all your account memberships. The CLI
equivalent is preloop auth logout --all. Existing access and refresh tokens
stop working, and you can sign in again with your normal sign-in method.
To sign another member out of the current account, open Settings → Users,
use their row's action menu and choose Sign out everywhere. The confirmation
names the member and explains the scope. With role-based access control, this
requires manage_users, the same permission used to deactivate a membership.
In the open-source edition, every active account member has these permissions.
The action is audited and leaves the membership active. Sessions in the
person's other accounts remain valid.
Live browser connections and CLI session viewers close when the revocation
event arrives. If delivery is unavailable, they check again every minute;
database outages can delay that check. The socket closes with code 4401,
and signing in again creates a new valid session.
API keys, managed-agent credentials and runner tokens are separate: signing someone out does not rotate or revoke them or stop their runners. Manage those credentials separately when needed. Removing a member is a different action that also revokes their account access and personal credentials.
Security settings list individual CLI logins and let you revoke one. Browser sessions are not listed individually; Sign out everywhere ends all of them within the action's scope.
Endpoint contract for the extension¶
All paths are under /api/v1. A 404 on a collection means "capability off" to
both clients. {kind} is one of ai_model, mcp_server, managed_agent,
flow, runner_pool or policy. For runner_pool the id is the pool name,
and for policy it is the id of the active baseline version.
| Method | Path | Body and notes |
|---|---|---|
| GET | /me/memberships |
{items:[{account_id, account_name, slug, parent_account_id, last_used_at}]} |
| POST | /auth/switch-account |
{account_id}, returns {access_token, refresh_token} |
| POST | /me/accounts |
{name}; creates a root account owned by the caller and returns {account:{id, name, parent_account_id}, membership}. The console then calls /auth/switch-account. 404 means the server predates it |
| GET, POST | /accounts/{id}/subaccounts |
POST {name, tags} |
| GET, PATCH, DELETE | /accounts/{id}/subaccounts/{sub_id} |
PATCH {name, tags}; 404 when sub_id is not a child of id |
| POST | /accounts/{id}/subaccounts/{sub_id}/detach |
|
| GET, POST | /accounts/{id}/access-grants |
{subject_type, subject_id, level, target, subaccount_ids} |
| DELETE | /accounts/{id}/access-grants/{grant_id} |
|
| GET | /accounts/{id}/shares?resource_type=&resource_id= |
A resource can have several shares |
| POST | /accounts/{id}/shares |
{resource_type, resource_id, target}; target is {type: all}, {type: selected, subaccount_ids} or {type: tag, key, value} |
| DELETE | /accounts/{id}/shares/{share_id} |
|
| GET | /accounts/{id}/shared-resources/{kind}/{resource_id} |
{kind, id, name, provider, identifier, description, price, shared_from}; never credentials |
| GET | /accounts/{id}/usage/rollup?subaccount_id=&start=&end= |
{rows:[{subaccount_id, subaccount_name, model, day, requests, cost_usd}]} |
| GET | /accounts/{id}/attention/rollup |
{items:[{subaccount_id, subaccount_name, count}]} |
| GET, PUT | /tags/{kind}/{resource_id} |
GET {tags, governed_keys, version}; PUT {tags, version} |
| GET, PUT | /access/rules |
GET {rules, inherited, modes, version}; PUT {rules, version} |
| GET | /access/rules/export |
{yaml} |
| POST | /access/rules/apply |
{yaml} |
| POST | /access/explain |
{subject, action, resource}, returns {effect, reason, rule_ids} |
| POST | /access/modes/preview |
{action, mode}, returns {losing_access, preview_token} |
| PUT | /access/modes/{action} |
{mode, preview_token} |
Tag and rule writes carry the version the client read. The server should
answer 409 when the stored set has a different version, and the clients then
reload instead of overwriting. A client that sends version: null has not
read a version (for example against a server that does not return one).
Moving an action to require_permit can remove access. The console enables
the save only after it has shown the preview, and sends the preview's
preview_token so the server can check that the preview was seen.