Source: https://docs.any.org/understanding/tech-space.html # The tech space Every account has exactly one **tech space**: a space derived from the account keys, with an owner-only ACL, that syncs only between that account's own devices. It is where any keeps everything that is *about the account* rather than about a shared space — starting with the list of spaces itself. ## What it is - **Derived.** Its id is a pure function of the account key; every device of the account computes the same id and re-creates it on first login. Its header carries `spaceType: any.techspace`. - **Owner-only.** The ACL has one member and the network refuses any further ACL record. Nothing in it can be shared; there is no such thing as a shared account. - **Unlisted.** It never appears in `GET /v1/spaces`, and its datasets are read through dedicated endpoints. Its id (`techSpaceId` on `GET /v1/account`) is a `:spaceId` only on a closed set of per-space routes — account-level bundles and the records and types on their roots; every other per-space route answers `405 space.unsupported`. It uses the same [record CRDT](https://docs.any.org/understanding/record-crdt.md.txt) as every other space, so writes to it sync, merge and converge like any other data. - **First.** It loads before regular spaces at boot, because the space list comes from it. ## What it holds The tech space carries one derived **index object** whose datasets are the account's system data: | Dataset | One row per | Read through | Written by | | --- | --- | --- | --- | | `spaces` | space the account participates in | `GET /v1/spaces`, `POST /v1/spaces/query[/subscribe]` | the SDK on create / join / delete, and the mirrors below | | `profile` | the account | `GET /v1/account`, `POST /v1/spaces/query` with `"dataset": "profile"` | `PUT /v1/account/metadata` | | `devices` | device (row id = peer id) | `GET /v1/devices`, `POST /v1/devices/query[/subscribe]` | `PUT /v1/devices/me`, `POST /v1/devices/activate`, engine boot | | `identities` | account identity ever encountered | `GET /v1/identities[/…]` | the SDK, from ACL records, one-to-one invites and the identity repository | | `inboxCursor` | the account | — | the coordinator-inbox receiver (one-to-one and direct-add invites) | | `crdtVersion` | the account | `GET /v1/health` (`crdtVersion`) | every release, on open ([Versioning](https://docs.any.org/understanding/versioning-and-reindex.md.txt)) | | `bundles` | account-level bundle, such as favourites | `GET /v1/spaces//bundles` | `POST /v1/spaces//bundles` ([Bundles](https://docs.any.org/collaboration/bundles.md.txt)) | A `spaces` row is the account's view of a space: ```json { "id": "bafy…", "type": "any.space", "name": "Team", "icon": "…", "remoteStatus": "active", "createdAt": { "$date": "2026-08-01T09:00:00.000Z" }, "ownRole": "writer", "push": { "spaceKey": "…", "encKey": "…", "encKeyId": "…" }, "settings": { "notifyMode": "mentions" }, "derived": false } ``` Fields mix scopes on purpose: `type`, `name`, `description`, `icon`, `remoteStatus`, `settings`, `derived` are **synced** across the account's devices, so a join pending (`joining`), a decline or a delete observed on one device converges the rest; `createdAt` is **derived** — stamped from the change that added the row (create for the author, join for a joiner), the same on every device; `ownRole` and `push` are **local** — each device derives them from the same converged inputs, so replicating them would only add lag — and so is `localStatus` (absent while the space is simply active), because a space offloaded on one device must stay loaded on another. Raw rows withhold private guest and invite keys. ## The space list is a view `GET /v1/spaces` maps `spaces` rows into `SpaceInfo`, filtering to active rows by default; `GET /v1/spaces?status=one_to_one_pending` or `status=deleted` selects the others. `POST /v1/spaces/query` and `POST /v1/spaces/query/subscribe` expose the raw rows through the same windowed primitive every dataset uses: ```bash curl -N -X POST http://127.0.0.1:7001/v1/spaces/query/subscribe \ -H 'content-type: application/json' \ -d '{"sort":["-createdAt"],"limit":50}' # event: ready → snapshot{records} → changes[{added,updated,removed}] … ``` Because the list is a dataset, everything that changes a row arrives as a row update on that stream: a join completing on another device, a rename, a role change, a push-key rotation, a settings edit ([Live space list](https://docs.any.org/realtime/space-list.md.txt)). The `dataset` field of these two endpoints accepts only `spaces` and `profile` — the `identities` rows carry each contact's profile-decryption key, and are read through `GET /v1/identities` where that key is stripped. ## Mirrors: how rows stay current Several row fields are **derived from other places** and mirrored in by SDK watchers on each device: | Field | Source of truth | Mirror | | --- | --- | --- | | `name`, `description`, `icon` | the target space's own `spaceIndex` object — encrypted in-space CRDT data, so every member sees a rename | each device's indexer hook writes the converged value onto the account's row after `PATCH /v1/spaces/:id` or a peer's edit, skipped when already equal | | `ownRole` | the space's ACL | the ACL mirror watcher, once at space load and once per applied ACL record | | `push.{spaceKey,encKey,encKeyId}` | ACL key material | the same watcher; `encKey` rotates with the read key | | `type` | the space header | backfilled once, set-once, on the first successful load of a row registered before the header was readable | A mirror is asynchronous, which shows up in one place: an immediate re-read after `PATCH /v1/spaces/:id` can briefly return the old name. Subscribe to the space list or to the `spaceIndex` object if you need the moment it lands. ## Account-scoped values A property declared `scope: account` is **shared across the account's devices but invisible to other members** — a personal flag on a shared object, or a chat's `notifyMode`. The tech space is its transport: for every target space the account is in, it holds one derived **carrier object** (`account_values` dataset, one record per `(objectId, dataset, recordId)`), and a per-device mirror replays converged carrier values into the target rows at their normal paths, stamped with the tech tree's versions. The write path is the ordinary one — `POST …/types/:t/properties` with `"scope": "account"`, then a value write — and reads see the value at its usual path. Deleting the object tombstones its carrier records; leaving or deleting the space drops the carrier ([System fields](https://docs.any.org/database/system-fields.md.txt)). Values of `scope: local` take the shorter route: written straight into the device's row, no DAG, no tech space. Dataset record fields can declare `account` but are not writable at that scope. ## Devices and the account's own state The `devices` dataset is what gives the account a notion of "my devices": one row per peer id with name, OS, version, installed apps and active-app claims, all synced through the tech space and nowhere else. The self row is upserted at engine boot; election of the active device for an app slug is computed by readers from the converged claim data, never from version ids ([Devices](https://docs.any.org/auth/devices.md.txt)). Per-space `settings` (the `notifyMode` for push, for example) live on the `spaces` row for the same reason — they are the account's preference about a space, not the space's data — and so sync to the account's other devices without ever reaching other members ([Push](https://docs.any.org/notifications/push.md.txt)). ## Why account-only Everything here is *about you*: which spaces you are in, what you call your devices, how you want to be notified, what you flagged for yourself. Keeping that in a space only your keys can open gives three properties at once: - **Multi-device for free.** A second device restored from the mnemonic derives the same tech space, pulls it first, and immediately knows every space to join, every device sibling, and every account-scoped value — before any shared space has loaded. - **Privacy by construction.** Other members never see your space list, your device names, your read markers or your settings, because those rows never enter a shared tree. - **One CRDT.** Account state converges by the same rules as everything else; there is no separate "preferences sync" with its own conflict story. > **Why it matters.** A hosted backend stores your account profile, device list and per-user preferences in tables only the server can join. any stores them in a space only you can decrypt, replicated by the same protocol as your documents — so "which devices are mine" is a query, and answering it needs no server at all. ## Further reading - [Accounts](https://docs.any.org/auth/accounts.md.txt) — mnemonic, account id, restoring a device. - [Spaces](https://docs.any.org/database/spaces.md.txt) — the `SpaceInfo` shape and status vocabulary. - [Identities](https://docs.any.org/auth/identities.md.txt) — the encrypted profile directory.