# Subaccounts and roles

Create, list, and disable Subaccounts, share them with role-based members, and target a Subaccount from JWT sessions and API keys.

Every Polyester user has one Main Account. API fields and some codes call it the **root account**, such as `rootAccountPublicId` and `API_KEY_ROOT_SCOPE_ONLY`. A Main Account can create **Subaccounts**: separate accounts with their own smart account, Funding and Trading balances, orders, API keys, policy, and history. Subaccounts let you isolate strategies, bots, or team desks without opening another login.

A Subaccount always belongs to the Main Account that created it. That Main Account is its owner, and ownership never moves. The owner can share a Subaccount with other Main Accounts by giving each one a role.

## Main Account and Subaccounts

| Account      | Identified by                                  | Controlled by                                                          |
| ------------ | ---------------------------------------------- | ---------------------------------------------------------------------- |
| Main Account | Your login session or a Main Account API key   | You, without a Subaccount policy                                       |
| Subaccount   | A public `subaccountId` plus its smart account | Its owner, invited members by role, and the attached Subaccount policy |

Subaccount identifiers are public IDs. ConnectRPC carries them as `fixed64` values and REST carries them as base58 strings. See [Public IDs](https://testnet.polyester.com/docs/developer-docs/shared-concepts/public-ids).

> **New Subaccounts start read-only**
>
> A Subaccount without an attached policy uses the system read-only policy. Even its owner cannot trade, transfer, or withdraw from it until a policy that allows those actions is attached. See [API key and Subaccount policies](https://testnet.polyester.com/docs/developer-docs/account/api-key-policies).

## Create a Subaccount

Creation is an interactive action. It needs a session token for the Main Account, current terms acceptance, and a signature from the new Subaccount's smart account. API keys cannot create Subaccounts.

1. Request a challenge

   Call [`CreateSubaccountChallenge`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountService/CreateSubaccountChallenge) ([`POST /v1/auth/subaccounts/challenge`](https://testnet.polyester.com/docs/api-docs/rest/POST/v1/auth/subaccounts/challenge)) with `ownerAddress`, the wallet bound to the Main Account, and `uri`, the requesting origin. The response returns the exact `message` to sign, the canonical next `smartAccountAddress`, its `smartAccountSaltNonce`, the `polyesterChainId`, and `expiresAt`, five minutes after issuance. A new challenge replaces the previous one.

2. Sign through the smart account

   Sign `message` with EIP-191 `personal_sign` semantics through the returned smart account. Deployed (ERC-1271) and counterfactual (ERC-6492) signatures are accepted. Do not hash, alter, or rebuild the message.

3. Create the Subaccount

   Call [`CreateSubaccount`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountService/CreateSubaccount) ([`POST /v1/auth/subaccounts`](https://testnet.polyester.com/docs/api-docs/rest/POST/v1/auth/subaccounts)) with the `smartAccountAddress`, `message`, `signature`, and an optional `label`, `icon`, and `color`. The response returns `subaccountId`, `totalCreated`, `smartAccountSaltNonce`, and the initial `revision`.

A missing terms acceptance fails with `FailedPrecondition` and `AUTH_TERMS_NOT_ACCEPTED`.

## List, read, and update Subaccounts

| Operation                   | Method                                                                                                                               | Callers            |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
| List                        | [`ListSubaccounts`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountService/ListSubaccounts)                   | Session or API key |
| Read one, with related data | [`GetSubaccount`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountViewService/GetSubaccount)                   | Session or API key |
| Read activity               | [`ListSubaccountActivity`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountViewService/ListSubaccountActivity) | Session or API key |
| Update label, look, status  | [`UpdateSubaccount`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountService/UpdateSubaccount)                 | Owner session      |

The REST routes are [`GET /v1/auth/subaccounts`](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/auth/subaccounts), [`GET /v1/auth/subaccounts/{subaccountId}`](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/auth/subaccounts/subaccount_id), [`GET /v1/auth/subaccounts/{subaccountId}/events`](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/auth/subaccounts/subaccount_id/events), and [`PATCH /v1/auth/subaccounts/{subaccountId}`](https://testnet.polyester.com/docs/api-docs/rest/PATCH/v1/auth/subaccounts/subaccount_id).

`ListSubaccounts` returns Subaccounts you own and Subaccounts shared with you, each with your `role` on it. `GetSubaccount` can also include members, invitations, the attached policy, current balances, and, for owners and admins, the Subaccount's API keys.

To include balances, set `includeBalances` (REST `?includeBalances=true`). `balances` then has the same shape as the [`GetBalances`](https://testnet.polyester.com/docs/api-docs/connect/ledger.read.v1.LedgerReadService/GetBalances) response: one row per Asset with its Funding, Trading, reserved, and available amounts. Session callers and API keys can both ask for it, with the same access as `GetBalances`:

- The caller needs `READ_BALANCES` on the Subaccount: its role, the Subaccount policy, and, for an API key, the key's own policy must allow it.
- An API key bound to the Subaccount can read its balances. A Main Account API key cannot read a Subaccount's balances, and a key bound to another Subaccount cannot either.
- A caller that may see the Subaccount but not its balances gets `PermissionDenied` with `AUTH_SUBACCOUNT_ACCESS_DENIED` (REST `403`) for the whole request. Omit `includeBalances` to read the rest of the view.
- When balances cannot be read right now, the request fails with `Unavailable` and `AUTH_TEMPORARILY_UNAVAILABLE` (REST `503`). Nothing changed; retry the same request.

`UpdateSubaccount` takes an `updateMask` and the `expectedRevision` from your latest read. A stale revision fails with a conflict instead of overwriting a newer change.

### Disable a Subaccount

There is no public delete method. To stop a Subaccount, its owner sets `status` to `DISABLED` with `UpdateSubaccount`. While it is disabled:

- new orders fail with `SUBACCOUNT_INACTIVE`, while cancellations still work
- internal transfers from it fail with `SOURCE_ACCOUNT_INACTIVE`, and transfers to it fail with `DESTINATION_INACTIVE`
- its active API keys are disabled, and new API keys cannot be created for it

Setting `status` back to `ACTIVE` re-enables the Subaccount. It does not reactivate its API keys. Set each key back to `ACTIVE` with [`UpdateApiKey`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.ApiKeyService/UpdateApiKey).

## Roles

The owner shares a Subaccount by inviting another Main Account with [`InviteSubaccountMember`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountService/InviteSubaccountMember). The invitee accepts with [`RespondSubaccountInvite`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountService/RespondSubaccountInvite). A member's role decides what that member may do on the Subaccount. The attached Subaccount policy can then narrow what any role, or any API key, can actually do.

| Role               | What the role allows                                                                                                                                   |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `OWNER`            | Everything, including status changes, the member MFA requirement, and the GuardSigner. Held only by the creating Main Account; not assignable.         |
| `ADMIN`            | Trading, transfers, withdrawals, deposit addresses, address book, members, invitations, API keys, and Subaccount policies, except owner-only controls. |
| `TREASURY`         | Internal transfers, withdrawals, address-book reads, auto-withdraw management, and deposit addresses. No trading or administration.                    |
| `TRADER`           | Spot trading, triggers, Swaps, and deposit addresses. No fund movement or administration.                                                              |
| `LEVERAGED_TRADER` | Currently the same permissions as `TRADER`. Leverage is not available.                                                                                 |
| `VIEWER`           | Read-only access, including deposit addresses. No writes.                                                                                              |

Every role can read the Subaccount, balances and equity, internal-transfer history, members, the attached policy, activity, deposit addresses, GuardSigner status, and the auto-withdraw rule. Every role except `TREASURY` can read spot orders and trades.

Owner-only controls include changing the Subaccount's status, changing the member MFA requirement, locking or unlocking policies, clearing an attached policy, and clearing an API key's policy. The owner role cannot be removed, downgraded, or replaced through member calls; such calls fail with `AUTH_SUBACCOUNT_OWNER_ROLE_LOCKED`.

### Read the live role catalog

Do not hard-code the table above. Read it from the API:

- [`ListSubaccountRoles`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountRoleService/ListSubaccountRoles) ([`GET /v1/auth/subaccount-roles`](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/auth/subaccount-roles)) is public. It returns every role, the stable `SubaccountPermission` values each role grants, and the `PolicyAction` that can further restrict each permission.
- [`GetEffectiveSubaccountPermissions`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountRoleService/GetEffectiveSubaccountPermissions) ([`GET /v1/auth/subaccounts/{subaccountId}/permissions`](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/auth/subaccounts/subaccount_id/permissions)) returns your current role and the permissions left after the attached policy is applied. It accepts session tokens only.

Effective permissions describe role and policy only. MFA, resource state, and limits can still deny a request.

### Member security

Inviting a member and changing a member's role with [`UpdateSubaccountMemberRole`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountService/UpdateSubaccountMemberRole) need a fresh step-up. The owner can require MFA for delegated members on protected actions with [`SetSubaccountMemberMFARequirement`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountService/SetSubaccountMemberMFARequirement). Disabling that requirement needs a fresh step-up; enabling it does not. See [Multi-factor authentication](https://testnet.polyester.com/docs/developer-docs/authentication-security/multi-factor-authentication).

## Target a Subaccount in API calls

Account-scoped methods accept an optional `subaccountId`. When you omit it, the request acts on the Main Account, or on the bound Subaccount for a Subaccount-scoped API key. How a Subaccount is selected depends on the credential.

## JWT session

The session identifies your Main Account. Add `subaccountId` to act on a Subaccount you own or that is shared with you. Polyester checks your role on that Subaccount, then the Subaccount policy. A Subaccount where you have no role is refused.

## Main Account API key

A key created without `subaccountId` acts on the Main Account only. Sending a `subaccountId` to an order, balance, transfer-history, or movement method is refused. Order and balance methods report `API_KEY_ROOT_SCOPE_ONLY`.

Deposit addresses are the exception: a Main Account key may create and list deposit addresses for Subaccounts its Main Account owns. See [Deposits](https://testnet.polyester.com/docs/developer-docs/funds-transfers/deposits).

## Subaccount-scoped API key

A key created with `subaccountId` acts on that Subaccount only. Omit `subaccountId` and the request uses the bound Subaccount. Naming a different Subaccount is refused; order and balance methods report `API_KEY_SUBACCOUNT_MISMATCH`.

Only the owner or an admin of a Subaccount can create keys scoped to it. The key belongs to the Subaccount's Main Account even when an admin created it.

An API key is never bound by a member role. Its limits are its own API key policy and, for a Subaccount, the Subaccount policy. Prefer Subaccount-scoped keys for automation, so a leaked key cannot reach other balances. See [Ed25519 API keys](https://testnet.polyester.com/docs/developer-docs/authentication-security/ed25519-api-keys) and [API key and Subaccount policies](https://testnet.polyester.com/docs/developer-docs/account/api-key-policies).

Field-level request and response schemas are in the [API reference](https://testnet.polyester.com/docs/api-docs). Error codes are listed under [Accounts, API keys, and security](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#accounts-api-keys-and-security).
