client.subaccounts creates, manages, shares, and audits the subaccounts visible to the
authenticated caller, and streams their updates. The nested client.subaccounts.policies sub-service manages the reusable permission templates you attach to subaccounts. Every method is
authenticated except the public listRoles catalog read.
Subaccounts isolate balances and permissions under your root account. Delegated members get a role, and a policy caps what any member can do. For the task-oriented walkthrough see the accounts and balances guide.
Methods
| Method | Summary |
|---|---|
list | List subaccounts owned by or shared with the caller. |
get | Fetch one subaccount with its keys, policy, members, and invites. |
createChallenge | Request the next smart account and its authorization message. |
create | Create a subaccount from a signature proof. |
update | Update a subaccount's label or status. |
inviteMember | Invite another account to a role on a subaccount. |
respondInvite | Accept, decline, or cancel an invitation. |
listInvites | List invitations by direction. |
listRoles | Read the public built-in role and permission catalog. |
getEffectivePermissions | Read the caller's role and permissions for a subaccount. |
listMembers | List a subaccount's owner and delegated members. |
updateMemberRole | Change a member's role in place. |
removeMember | Revoke a member's delegated access. |
setMemberMfaRequirement | Toggle the owner-controlled MFA gate. |
listEvents | Page the subaccount audit trail. |
subscribe | Stream subaccount updates. |
subscribeApiKeys | Stream API key updates. |
list(options?)
Returns { totalCreated, subaccounts } for the accounts owned by or shared with the caller. totalCreated counts every subaccount ever created, including soft-deleted ones. Use createChallenge for the canonical next smart-account address and
salt nonce.
const { totalCreated, subaccounts } = await client.subaccounts.list();
console.log(`${subaccounts.length} active of ${totalCreated} created`);get(input, options?)
Fetches one subaccount as an aggregated dashboard view: the subaccount plus its apiKeys, policy, members, and outgoing invites. Throws if the subaccount is missing.
const detail = await client.subaccounts.get({ subaccountId });
console.log(detail.label, detail.policy.name, detail.members.length);SubaccountIdInput is { subaccountId }.
createChallenge(input, options?)
Requests the canonical next subaccount smart account and a short-lived EIP-191 authorization message
bound to the authenticated root account and the selected owner EOA. Sign the exact message through
the returned smart account, then pass it to create. A new challenge replaces the previous one.
const challenge = await client.subaccounts.createChallenge({
ownerAddress: rootOwnerAddress,
uri: window.location.origin,
});
const signer = deriveSubaccountSigner(challenge.smartAccountSaltNonce);
const signature = await signer.signMessage(challenge.message);interface SubaccountChallenge {
message: string;
smartAccountAddress: string;
smartAccountSaltNonce: number;
expiresAt?: number; // epoch ms
polyesterChainId: number;
}An expired, replaced, or replayed authorization fails create with SubaccountChallengeInvalidError; request a fresh challenge and sign again.
create(input, options?)
The root account must have accepted the current terms. Use client.auth.acceptTerms() from an
interactive JWT session after the user agrees. Missing acceptance produces the typed auth detail AUTH_TERMS_NOT_ACCEPTED.
Creates a subaccount under the caller's root account using the smart-account address, exact
message, and smart-account signature from createChallenge, returning a CreateSubaccountResult.
const result = await client.subaccounts.create({
label: "Trading bot",
smartAccountAddress: challenge.smartAccountAddress,
message: challenge.message,
signature,
});
console.log(result.subaccountId, result.smartAccountSaltNonce);client.auth.createSubaccount: it requests the challenge and signs it with an
account signer for you, so you never assemble the proof by hand.interface CreateSubaccountInput {
smartAccountAddress: string;
message: string;
signature: string;
label?: string;
icon?: string;
color?: string;
}
interface CreateSubaccountResult {
subaccountId: string;
totalCreated: number;
smartAccountSaltNonce: number;
revision: string;
}Sign the challenge message exactly as issued. This proof may use a Safe or ERC-6492-wrapped signature.
update(input, options?)
Updates mutable display and status fields such as label and status.
await client.subaccounts.update({
subaccountId,
expectedRevision: detail.revision,
label: "Renamed",
});interface UpdateSubaccountInput {
subaccountId: string;
expectedRevision: string;
label?: string;
icon?: string;
color?: string;
status?: "active" | "disabled";
}The SDK does not delete subaccounts. Disable one to prevent further use:
await client.subaccounts.update({
subaccountId,
expectedRevision: detail.revision,
status: "disabled",
});Updates use optimistic concurrency. Refetch after a RevisionConflictError and use the latest revision; never retry the stale mutation unchanged.
inviteMember(input, options?)
Creates or refreshes a pending invitation granting a role on a subaccount to another root account,
returning the SubaccountInvite.
const invite = await client.subaccounts.inviteMember({
subaccountId,
granteeAccountId,
role: "trader",
});interface InviteSubaccountMemberInput {
subaccountId: string;
granteeAccountId: string;
role: "owner" | "admin" | "treasury" | "leveraged_trader" | "trader" | "viewer";
}respondInvite(input, options?)
Accepts or declines an incoming invite, or cancels an outgoing one, returning the updated invite.
await client.subaccounts.respondInvite({ inviteId, action: "accept" });interface RespondSubaccountInviteInput {
inviteId: string;
action: "accept" | "decline" | "cancel";
}listInvites(input, options?)
Returns invitations for the caller, newest first, filtered by direction.
const incoming = await client.subaccounts.listInvites({ direction: "incoming" });interface ListSubaccountInvitesInput {
direction?: "incoming" | "outgoing" | ""; // "" lists all
}listRoles(options?)
Returns the public built-in role and permission catalog. Each role has a role, displayName, description, assignable flag, and its permission labels. Each permission has a permission, displayName, description, and matching policyAction.
const { roles, permissions } = await client.subaccounts.listRoles();
const trader = roles.find((role) => role.role === "trader");
console.log(trader?.permissions, permissions.length);getEffectivePermissions(input, options?)
Returns the authenticated caller's role, effective permissions, and attached subaccountPolicyId for one subaccount. subaccountPolicyId is "" when the subaccount uses the
placeholder policy, on both this result and Subaccount reads.
const effective = await client.subaccounts.getEffectivePermissions({ subaccountId });
console.log(effective.role, effective.permissions, effective.subaccountPolicyId);listMembers(input, options?)
Returns the owner and delegated members of a subaccount, each with role and MFA enrollment status.
const members = await client.subaccounts.listMembers({ subaccountId });
for (const member of members) {
console.log(member.username, member.role, member.mfaEnrolled);
}updateMemberRole(input, options?)
Changes an existing member's role without sending a new invitation.
await client.subaccounts.updateMemberRole({
subaccountId,
granteeAccountId,
role: "viewer",
});removeMember(input, options?)
Revokes a member's delegated access by grantee account ID.
await client.subaccounts.removeMember({ subaccountId, granteeAccountId });setMemberMfaRequirement(input, options?)
Enables or disables the owner-controlled MFA gate for delegated interactive member actions on a subaccount.
await client.subaccounts.setMemberMfaRequirement({
subaccountId,
requireMemberMfa: true,
});listEvents(input, options?)
Returns the subaccount audit trail, newest first, with { events, nextPageToken }. limit is
capped at 200; page through with the returned token.
let pageToken = "";
do {
const page = await client.subaccounts.listEvents({ subaccountId, limit: 200, pageToken });
console.log(page.events.length);
pageToken = page.nextPageToken;
} while (pageToken !== "");Each SubaccountEvent carries entityKind, eventAction, source, actorAccountId, a parsed payloadJson, and a createdAt timestamp.
subscribe(input)
Streams live subaccount updates over a private channel and returns an idempotent unsubscribe
function. Takes an accountId plus onEvent (required) and optional onOpen, onClose, and onError. See the realtime client reference for the
handler contract.
const unsubscribe = client.subaccounts.subscribe({
accountId,
onEvent: (subaccount) => console.log(subaccount.id, subaccount.status),
onError: (ctx) => console.error(ctx.channel, ctx.error),
});
// later
unsubscribe();subscribeApiKeys(input)
Streams live API key updates for an account over a private channel, with the same handler contract
and unsubscribe behavior as subscribe.
const unsubscribe = client.subaccounts.subscribeApiKeys({
accountId,
onEvent: (apiKey) => console.log(apiKey.keyId, apiKey.status),
});Policies
client.subaccounts.policies manages reusable permission templates: spot-market scopes, allowed
actions, order limits, trading halts, review times, expiry, and policy locks. Attach a policy to a
subaccount to cap what its members can do. Every method is authenticated.
| Method | Summary |
|---|---|
list | List policy templates visible to the caller. |
get | Fetch one policy by ID, or null. |
create | Create a policy, optionally attaching it to a subaccount. |
update | Patch selected fields on a policy. |
delete | Delete a policy that is not in use. |
apply | Attach a policy to a subaccount, or clear it. |
subscribePolicies | Stream policy updates. |
policies.list(options?)
Returns the policy templates visible to the caller, sorted by ascending policy ID.
const policies = await client.subaccounts.policies.list();policies.get(input, options?)
Fetches one policy template by base58 policy ID, returning null when none is found.
const policy = await client.subaccounts.policies.get({ policyId });policies.create(input, options?)
Creates a policy template from its scopes, actions, and limits, optionally attaching it to a target
subaccount in the same request via subaccountId. Returns the created SubaccountPolicy.
const policy = await client.subaccounts.policies.create({
name: "Read-only",
spotMarketScope: "all",
actions: ["read-spot", "read-balances", "read-internal-transfers", "read-address-book"],
maxOrderSize: "0",
subaccountId, // optional: attach on create
});policies.update(input, options?)
Patches an existing policy template. Pass policyId, expectedRevision from the policy you last
read, and only the fields you want to change. If another writer updated the policy first, you get RevisionConflictError.
await client.catalog.ensureReady();
const symbolId = client.catalog.market.requireSymbolIdByPairSymbol("BTC-USDT");
await client.subaccounts.policies.update({
policyId,
expectedRevision: policy.revision,
name: "Limited trader",
spotMarketScope: "allowlist",
spotMarkets: [{ symbolId }],
actions: ["read-balances", "read-spot", "trade-spot"],
maxOrderSize: "5000",
});maxOrderSize is a decimal USDT string, with at most six decimal places. It is returned in the
same form. "0" means no order-notional cap.
policies.delete(policyId, options?)
Deletes a policy template. Only works when the policy is not attached to any subaccount.
await client.subaccounts.policies.delete(policyId);policies.apply(input, options?)
Attaches a policy to a subaccount. Passing policyId: null clears the explicit binding and restores
the system read-only policy: read-balances, read-internal-transfers, read-address-book, and read-spot.
// Attach
await client.subaccounts.policies.apply({ subaccountId, policyId });
// Clear
await client.subaccounts.policies.apply({ subaccountId, policyId: null });interface SubaccountPolicyApplyInput {
subaccountId: string;
policyId: string | null; // null detaches the policy
}policies.subscribePolicies(input)
Streams live policy updates over a private channel and returns an idempotent unsubscribe function.
Takes an accountId plus the standard handler fields.
const unsubscribe = client.subaccounts.policies.subscribePolicies({
accountId,
onEvent: (policy) => console.log(policy.id, policy.name),
});Related
- Accounts and balances guide for the task-oriented walkthrough.
- Auth for
client.auth.createSubaccountand account switching. - API keys for the keys returned by
get. - MFA for the member MFA requirement gate.
- Errors for validation and lookup error types.