11 — Auth and Accounts
A Picasso account is needed only for managed inference, memory sync, cloud sessions and the account dashboard at https://picassohq.com/account. Bring-your-own-key runs work without signing in. The CLI signs in with a device code. The CLI and Picasso for Mac share one sign-in on a Mac: a single Keychain item, so signing in or out on one signs the other in or out too.
Account model
GET /v1/me returns the signed-in account:
{ "account": { "id": "<id>", "email": "<email>", "plan": "free" } }The launch release ships solo free-tier accounts. The organization, team, SSO and SCIM shapes in earlier drafts are design only. The API has an org-scoped GET /v1/auth/sso/providers?organization_id=… route that answers 503 sso_provider_not_configured when no SSO adapter is configured, and no client uses it at launch.
Device-code login
$ picasso auth login
Picasso Terms version <v> (effective <YYYY-MM-DD>): https://picassohq.com/legal/terms
In the free tier, new coding sessions are captured under these Terms.
Do you accept the Picasso Terms? [y/N] y
Picasso device login started.
Verification: https://picassohq.com/auth/device?code=1A2B-3C4D
Code: 1A2B-3C4D
Picasso device login complete.
Signed in as alice@example.com (free). Token stored in the OS keychain.
Picasso Terms: version <v> accepted <YYYY-MM-DD>.The default API is https://api.picassohq.com. --api-url <url> or PICASSO_API_URL overrides it.
The flow:
- The CLI reads the published Terms (
GET /v1/account/terms). Before the device flow starts, they must be accepted, either at the[y/N]prompt on a terminal or with--accept-termsfor scripts and CI. A non-interactive login without--accept-termsexits 2 withSigning in requires accepting the Picasso Terms (version <v>): https://picassohq.com/legal/terms. Re-run with --accept-terms to accept them.Answering no exits 1 withSign-in cancelled: the Picasso Terms were not accepted. - The CLI calls
POST /v1/auth/device/authorizeand prints the verification URL and code. - The CLI polls
POST /v1/auth/device/tokenat the server'sinterval, adding 5 s onslow_down. Both calls sendx-picasso-client: cli/<version>andx-picasso-installation-id: <uuid>. - The user opens the URL on any device, signs in, and enters the code.
- The API returns one bearer token. The CLI stores it (see below), then records the Terms acceptance (
POST /v1/account/terms-acceptances,surface:"cli"). A failed acceptance record never blocks sign-in. It is reported, and nothing is captured until a later login records it.
Exit codes: 0 signed in, 1 network or API error, 3 denied (Device login was denied.) or expired (Device login expired before approval completed.).
Token lifetime. The token lasts 30 days. There is no refresh token and no silent refresh. Before each use the CLI checks expiresAt with a 60 s skew. An expired token is deleted and reported as signed out, and the user runs picasso auth login again.
Sign-out and status
| Command | Output | Exit | |||
|---|---|---|---|---|---|
picasso auth status | Signed in as <email> · plan <plan> · <api origin> · expires <YYYY-MM-DD> | 0 | |||
Not signed in. Run \picasso auth login\. (no token) | 3 | ||||
Signed out — run \picasso auth login\`` (expired, or the server answered 401) | 3 | ||||
Your Picasso account is suspended. Contact support@picassohq.com. (403 account_suspended) | 3 | ||||
picasso auth status --json | {signedIn:true, apiUrl, account{id,email,plan}, expiresAt, tokenStorage, installationId}, or `{signedIn:false, reason:"missing"\ | "expired"\ | "revoked"\ | "suspended", apiUrl}` | 0 / 3 |
picasso auth logout | Signed out of Picasso. This device's session was revoked. | 0 | |||
Not signed in. | 0 | ||||
picasso: Signed out on this device. warning: the server session could not be revoked (<reason>); it expires <ISO>. | 1 |
auth status calls GET /v1/me. On a 401 it deletes the local token. On a 403 account_suspended it keeps the token, and managed and cloud calls stay blocked until an admin reinstates the account. A network failure exits 1.
auth logout calls POST /v1/auth/logout with the bearer token, then deletes the stored token. A 401 from logout counts as revoked. The token is deleted even when the revoke fails, and the command then exits 1 so scripts know the server session stays valid until it expires. Because the Keychain item is shared, logging out of the CLI also signs out Picasso for Mac for that API origin. When PICASSO_API_TOKEN is set, logout prints PICASSO_API_TOKEN is set in the environment; unset it to sign out. and changes nothing.
The same 401 and 403 mapping applies to every managed and cloud call (managed runs, memory sync, cloud session upload). A 401 deletes the local token and fails with picasso: Signed out — run \picasso auth login\` (exit 3). A 403 account_suspended keeps the token and fails with picasso: Your Picasso account is suspended. Contact support@picassohq.com. (exit 3). Signed-in users can see and revoke their own devices' sessions at https://picassohq.com/account (GET /v1/account/sessions`).
Credential storage
Picasso account token. It is a generic password in the macOS login Keychain:
- service
com.picassohq.account-session - account = the normalized API origin (e.g.
https://api.picassohq.com)
The CLI and the Mac app read and write the same item (@picasso/shared-home readAccountSession/writeAccountSession). The secret is a JSON record:
{ "schemaVersion": 1, "accessToken": "<bearer>", "tokenType": "Bearer", "expiresAt": 1767225600000,
"accountId": "<id>", "email": "<email>", "serverSessionId": "<id>", "signedInBy": "cli",
"installationId": "<uuid>", "apiOrigin": "https://api.picassohq.com", "updatedAt": "<ISO>" }- Writes. The item is created with
/usr/bin/security add-generic-password -T /usr/bin/security -T /Applications/Picasso.app, so both clients get the same ACL whichever one signed in. An existing item is updated with-U. The token never appears on argv: the CLI runssecurity -iand sends the command on stdin, with the value hex-encoded (-X). Reads usesecurity find-generic-password -w. - Linux. The same service and account attributes go through libsecret (
secret-tool), with the secret on stdin. - File fallback. When no keychain backend works (for example, headless Linux without
secret-tool), the CLI printswarning: no system keychain is available; the Picasso token is stored in ~/.picasso/login.json (mode 0600).and writeslogin.jsonv2 withtokenStorage:"file".PICASSO_TOKEN_STORAGE=fileforces the file store.PICASSO_TOKEN_STORAGE=keychaindisables the fallback, so a keychain failure becomes an error. - Migration. An older
~/.picasso/login.jsonthat still holds a token (v1, or v2 with an inline token) is moved into the Keychain item once (migrateLoginJson) and then deleted. - Environment override.
PICASSO_API_TOKENshort-circuits the stores (for CI and scripts). It is never written back.
Installation id. ~/.picasso/installation-id holds one UUIDv4 (mode 0600) per machine, created on first API use and sent as x-picasso-installation-id. Admins see it, with the surface (cli/mac) and last-seen time, on each account session.
Other credentials. Provider API keys (BYO mode) and MCP secrets are stored per provider route by the engine's credential store and are covered in 07-providers.md and 05-mcp.md. Subscription passthrough is not available in this release (17-subscription-passthrough.md). The memory sync key has its own Keychain item, com.picassohq.memory-key (see 15-memory.md and picasso memory key).
Client config and minimum version
At startup (except update, auth, help, --help and --version), the CLI reads GET /v1/client-config, cached in ~/.picasso/client-config.json for ttl_seconds (300 s by default). A network failure fails open. PICASSO_CLIENT_CONFIG=off skips the fetch. The CLI exits 1 with picasso: Picasso <cur> is no longer supported (minimum <min>). Run: npm i -g @picassohq/cli@latest in three cases:
- the version is below
min_versions.cli; - the version is below
forced_update.target_versionafter its deadline; update_requiredis true.
Before the deadline, a forced update prints a warning instead.
Session-level identity
Every cloud-synced session and memory record is scoped to the account that owns it. Local sessions stay on the machine. Cloud session upload is opt-in (picasso settings set sync.cloudSessions on; see 06-sessions.md).
Account deletion
The CLI has no account-deletion command in this release. Deletion is a privacy request handled through the admin compliance flow. It erases the account's data in one transaction and returns a receipt. To remove the local sign-in, run picasso auth logout. picasso uninstall --purge removes local data, and only *records* a keychain purge request for platform uninstallers: it does not delete Keychain items itself.
Acceptance criteria
picasso auth statuson a clean home printsNot signed in. Run \picasso auth login\.and exits 3.- After
picasso auth login,auth status --jsonreports"tokenStorage": "keychain", andsecurity find-generic-password -s com.picassohq.account-sessionfinds the item. Picasso for Mac shows the same account without signing in again. - An admin suspension makes
auth statusprint the suspended message and exit 3, with the token kept. An admin session revoke makes it printSigned out — run \picasso auth login\`` and exit 3, with the token deleted. picasso auth logoutexits 0, and the old bearer token then gets401from/v1/me.
Dependencies
apps/api: device flow,/v1/me,/v1/auth/logout,/v1/account/*,/v1/client-config.packages/shared-home:account-session.ts(the shared Keychain item) andinstallation.ts.apps/cli/src/login-store.ts,api-client.ts,auth-provider-commands.ts,client-config.ts.apps/marketing/app/account: the account dashboard.