Source: https://docs.any.org/auth/accounts.html
# Accounts
An account is derived from a twelve-word BIP-39 mnemonic. The same phrase always yields the same account id, while each machine that restores it mints its own device key — that is how a second device joins without cloning the first.
## Mnemonic ⇒ account id
```
mnemonic + index ──► account key ──► account id
```
The derivation `index` defaults to **1**, the `any` account index. Index 0 is reserved for accounts derived by anytype, so one phrase can serve both products with two distinct accounts. Restoring an anytype-derived account needs an explicit index 0.
> **Note.** The mnemonic is the only recovery path. `any` never stores it; it is printed once at generation and never again. Back it up before writing data you care about.
## First run: `any init`
```bash
any init # generate a fresh account, print the mnemonic once
any init --mnemonic-stdin < phrase.txt # restore an existing account (fresh device key)
any init --mnemonic "w1 … w12" --index 0 # restore an anytype-derived account
any init --new # force an additional fresh account
```
| Flag | Effect |
| --- | --- |
| *(none)* | generate a fresh account under `//`; a no-op that lists accounts when any already exist |
| `--mnemonic`, `--mnemonic-stdin` | authorize an existing account; prefer stdin so the phrase stays out of shell history |
| `--index N` | derivation index for `--mnemonic`; default 1 |
| `--new` | create an additional account even when one exists |
Restoring writes a new `wallet.key` with a **freshly generated device key**. Never copy `wallet.key` between machines: that clones the device key, the two peers then present one network identity, and realtime sync between them breaks.
## Who owns the server: `--mode`
`any run --mode standalone` (the default) is **the user's** server: keys live in `wallet.key`, the account resolves from the data dir, and logout or HTTP shutdown are refused — stop it with `any stop` or Ctrl-C. `any run --mode managed` is **a host's** server (the desktop shell, a mobile app): it never resolves an account from disk, the host supplies the recovery phrase over `POST /v1/auth` on every launch and the account key never touches disk, and sign-out, account switching and `POST /v1/shutdown` are accepted behind a **control token** only the host holds. A CLI-spawned managed server prints the token as the second stdout line (`CONTROL_TOKEN `, after `LISTENING `); an in-process host passes its own. The token travels as the `X-Any-Control-Token` header; without it those calls answer `403 control.forbidden`.
The mode is fixed at launch and cannot be changed over HTTP. Clients never branch on it directly: `GET /v1/auth` reports what the server accepts as `capabilities` bits.
A managed login carries the account key only. The device key — what the peerId derives from — is minted once per account at `//device.key` and reused on every later login, so logging in on every app launch does not register a new device each time.
## Authorizing over HTTP: `/v1/auth`
`any run` does not create wallets. When no account resolves from the data dir — a fresh root, several accounts and no `--account` / `ANY_ACCOUNT` selector, or any managed server — the server starts **unauthorized**. Every `/v1` route except `/v1/health`, `/v1/shutdown`, `/v1/openapi.json` and `/v1/auth` returns `401 auth.required`.
```bash
curl -s http://127.0.0.1:7001/v1/auth
```
```json
{ "authorized": false,
"mode": "standalone",
"capabilities": { "deauthorize": false, "switchAccount": false, "shutdown": false },
"accounts": [ { "id": "A8tR…", "default": true }, { "id": "A8g1…" } ] }
```
A managed server reports every capability `true` and an empty `accounts` list — it holds no keys, so the client owns the account list.
`POST /v1/auth` boots the engine in place — no restart. `mnemonic` and `accountId` are mutually exclusive:
```bash
# generate
curl -s -X POST http://127.0.0.1:7001/v1/auth -d '{}'
# restore at index 1 (device key freshly generated)
curl -s -X POST http://127.0.0.1:7001/v1/auth -d '{"mnemonic": "w1 … w12"}'
# restore an anytype-derived account
curl -s -X POST http://127.0.0.1:7001/v1/auth -d '{"mnemonic": "w1 … w12", "index": 0}'
# select a wallet already on disk (standalone only)
curl -s -X POST http://127.0.0.1:7001/v1/auth -d '{"accountId": "A8g1…"}'
# managed: switch to another account in place
curl -s -X POST http://127.0.0.1:7001/v1/auth -H "X-Any-Control-Token: $TOKEN" \
-d '{"mnemonic": "w1 … w12", "replace": true}'
```
```json
{ "accountId": "A8g1…",
"created": true,
"mnemonic": "w1 … w12" }
```
`created` reports that the account had no local state before this call. `mnemonic` is present **only** when the server generated it. `index` is valid only together with `mnemonic`; a selected account's index is baked into its wallet.
While an account is running, the reply follows the account the request derives to — the server never reads a wallet or boots anything to decide:
| | `{}` | same account | different account |
| --- | --- | --- | --- |
| standalone | `409 auth.already_authorized` | `200 {"alreadyAuthorized": true}` | `403 auth.not_managed` |
| managed | `409 auth.already_authorized` | `200 {"alreadyAuthorized": true}` | `409 auth.account_mismatch`, or a switch with `"replace": true` |
The same account is always a no-op — a retry never drops your streams, and `alreadyAuthorized` on a mnemonic request confirms the phrase you hold belongs to the running account (an `accountId` request confirms only the id). A refusal never echoes the account a rejected phrase derives to. A switch ends every open stream with `closed{"reason": "deauthorized"}`; if the new account fails to boot after the teardown the server is left unauthorized — re-read `GET /v1/auth`.
`DELETE /v1/auth` (managed, control token) signs out in place: the engine is torn down, streams end with `deauthorized`, and the server stays up unauthorized. Delete the stored phrase too, or the account is not actually signed out on that device.
CLI equivalents:
```bash
any auth login # generate
any auth login --mnemonic-stdin # restore
any auth login --account A8g1… # select (standalone)
ANY_CONTROL_TOKEN=… any auth login --mnemonic-stdin --replace # switch (managed)
ANY_CONTROL_TOKEN=… any auth logout # DELETE /v1/auth (managed)
any auth status # GET /v1/auth
```
Pass the control token through `ANY_CONTROL_TOKEN`; `--control-token` exists too, but a flag value is visible in the process list.
If the engine fails to boot after this call created the account dir, the half-created dir is removed so a retry starts clean instead of auto-selecting an un-backed account.
| Status | Code | When |
| --- | --- | --- |
| 400 | `auth.bad_mnemonic` | phrase fails BIP-39 validation |
| 400 | `request.invalid_field` | `mnemonic` + `accountId` together, `index` without `mnemonic`, `replace` without a credential, or `accountId` on a managed server |
| 403 | `control.forbidden` | managed server, control token missing or wrong |
| 403 | `auth.not_managed` | standalone server: a different account, or `DELETE` |
| 404 | `auth.account_not_found` | `accountId` has no local wallet |
| 409 | `auth.account_in_use` | another process holds that account's instance lock |
| 409 | `auth.account_mismatch` | managed server runs another account; pass `replace: true` to switch |
| 409 | `auth.mnemonic_mismatch` | the wallet on disk disagrees with the supplied phrase / index |
| 409 | `auth.already_authorized` | `{}` while an account runs — a fresh account is never minted in place |
| 400 | `auth.passkey_required` | encrypted wallet with a missing or wrong passkey; the passkey comes from the configured env var, never the body |
| 409 | `sdk.crdt_version_newer` | the account's data was written by a newer release; upgrade before opening it |
| 500 | `auth.device_key_corrupt` | managed: the cached `device.key` is unreadable; remove it to mint a new device identity (this install then registers as a new peer) |
## One server, one account
A process serves one account at a time. A managed server's host switches in place (`replace`) or signs out (`DELETE /v1/auth`); a standalone server refuses both — stop it and start with `--account `, or let `POST /v1/auth` pick on an unauthorized server. Two accounts at once means two `any run` processes on different ports; they may share one data-dir root because every account dir carries its own instance lock.
## Data dir layout
The data dir is a root that can hold several accounts:
```
/ # default ~/.any
├── config.yaml
├── models/ # embedder model cache, shared by all accounts
├── wallet.key # legacy flat layout = the DEFAULT account
├── server.lock server.pid # (its sdk/ and index/ sit directly at the root)
└── /
├── wallet.key # standalone: the wallet, mode 0600
├── device.key # managed: the cached device key, mode 0600 — never copy it
├── server.lock # per-account single-instance lock (OS file lock)
├── server.pid # holder's pid, for error messages only
├── server.addr # holder's bound address, read by the CLI
├── sdk/ # any-store databases, owned by the SDK
├── files/ # file content, owned by the SDK
└── index/ # local search index
```
Account selection at boot (standalone): `auth.walletPath` / `--wallet` → that wallet; `account:` / `ANY_ACCOUNT` / `--account` → `//`; otherwise a root `wallet.key` is the default, else a sole `//` dir, else no account. A managed server selects nothing — the host states the account on every launch. Data lives under `//` in both modes, so the same account reached under either custody finds its existing data; the legacy flat-root account is standalone-only. `GET /v1/health` reports the booted account in `account` (`""` while unauthorized).
> **Why it matters.** Because the account is a key and the data dir is just files, there is nothing to migrate between hosting providers and nothing a provider can lock: restoring is "type twelve words on a new machine", and every space the account belongs to syncs back down from the network.
See also [Devices](https://docs.any.org/auth/devices.md.txt) for what the fresh device key registers, and the [operations data-dir page](https://docs.any.org/operations/data-dir.md.txt) for what is safe to delete.