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.
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.
Request a challenge
Call CreateSubaccountChallenge (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.
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.
Create the Subaccount
Call CreateSubaccount (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 | Session or API key |
| Read one, with related data | GetSubaccount | Session or API key |
| Read activity | ListSubaccountActivity | Session or API key |
| Update label, look, status | UpdateSubaccount | Owner session |
The REST routes are GET /v1/auth/subaccounts, GET /v1/auth/subaccounts/{subaccountId}, GET /v1/auth/subaccounts/{subaccountId}/events,
and PATCH /v1/auth/subaccounts/{subaccountId}.
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 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_BALANCESon 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
PermissionDeniedwithAUTH_SUBACCOUNT_ACCESS_DENIED(REST403) for the whole request. OmitincludeBalancesto read the rest of the view. - When balances cannot be read right now, the request fails with
UnavailableandAUTH_TEMPORARILY_UNAVAILABLE(REST503). 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 withDESTINATION_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.
Roles
The owner shares a Subaccount by inviting another Main Account with InviteSubaccountMember. The invitee accepts with 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(GET /v1/auth/subaccount-roles) is public. It returns every role, the stableSubaccountPermissionvalues each role grants, and thePolicyActionthat can further restrict each permission.GetEffectiveSubaccountPermissions(GET /v1/auth/subaccounts/{subaccountId}/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 need a fresh step-up. The owner can require MFA for
delegated members on protected actions with SetSubaccountMemberMFARequirement. Disabling that
requirement needs a fresh step-up; enabling it does not. See 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.
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.
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.
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 and API key and Subaccount policies.
Field-level request and response schemas are in the API reference. Error codes are listed under Accounts, API keys, and security.