Create Subaccount (WebSocket)
Create a new trading subaccount under the authenticated wallet address through the WebSocket connection. Subaccounts allow for isolated trading strategies, separate margin management, and organized position tracking within a single master account.
Endpoint
ws.send() wss://api.synthetix.io/v1/ws/tradeRequest
Request Format
{
"id": "create-subaccount-1",
"method": "post",
"params": {
"action": "createSubaccount",
"name": "Trading Bot Strategy",
"nonce": 1735689600000,
"expiresAfter": 1735689900,
"signature": {
"v": 28,
"r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
}
}
}Parameters
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Client-generated unique request identifier |
method | string | Yes | Must be "post" |
params | object | Yes | Contains all parameters for the request |
Params Object
| Parameter | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Must be "createSubaccount" |
name | string | No | Optional display name for the new subaccount (max 50 chars) |
nonce | integer | Yes | Positive integer, incrementing nonce |
expiresAfter | integer | No | Optional expiration timestamp in seconds (0 = no expiration) |
signature | object | Yes | EIP-712 signature for wallet authentication |
Important: This endpoint must be sent over a WebSocket connection already authenticated to an owned subaccount. The EIP-712 payload signs that authenticated subaccount as masterSubAccountId to prove ownership before creating the new subaccount.
Naming Guidelines
- Optional Parameter: Name can be omitted for system-generated naming
- Character Limit: Maximum 50 characters when specified
- Default Behavior: If no name provided, system may generate a default identifier
Examples: "Main Trading", "DCA Strategy", "Arbitrage Bot", "Risk Management"
Response Format
Success Response (New Account)
When a subaccount is first created, subAccountId, subAccountName, and accountLimits are populated. All other fields return zero/null values:
{
"id": "create-subaccount-1",
"status": 200,
"result": {
"subAccountId": "1867542890123456790",
"masterAccountId": null,
"subAccountName": "Trading Bot Strategy",
"collaterals": null,
"crossMarginSummary": {
"accountValue": "",
"availableMargin": "",
"totalUnrealizedPnl": "",
"maintenanceMargin": "",
"initialMargin": "",
"withdrawable": "",
"adjustedAccountValue": "",
"debt": ""
},
"positions": null,
"marketPreferences": {
"leverages": null
},
"feeRates": {
"makerFeeRate": "",
"takerFeeRate": "",
"tierName": ""
},
"accountLimits": {
"maxBorrowCapacity": "100000",
"maxOrdersPerMarket": 50,
"maxSubAccounts": 10,
"maxTotalOrders": 200
}
}
}Response Fields
| Field | Type | Description |
|---|---|---|
subAccountId | string | System-generated unique identifier for the new subaccount |
masterAccountId | string | null | Master account ID (returns null) |
subAccountName | string | Display name provided in request (or empty if not specified) |
collaterals | array | null | Collateral balances (returns null for new accounts) |
collaterals[].adjustedCollateralValue | string | Adjusted collateral value after applying haircuts |
collaterals[].calculatedAt | integer | Unix millisecond timestamp of the collateral price calculation (0 for USDT) |
collaterals[].collateralValue | string | Raw collateral value |
collaterals[].pendingWithdraw | string | Amount pending withdrawal |
collaterals[].price | string | Collateral price used for valuation |
collaterals[].quantity | string | Total collateral quantity held |
collaterals[].symbol | string | Collateral asset symbol |
collaterals[].withdrawable | string | Amount available to withdraw |
crossMarginSummary | object | Cross-margin account summary (returns empty strings for new accounts) |
crossMarginSummary.debt | string | USDT debt after positive unrealized PnL offset |
positions | array | null | Open positions (returns null for new accounts) |
marketPreferences | object | Market-specific preferences like leverage |
feeRates | object | Fee rate information |
accountLimits | object | Account limit information |
accountLimits.maxBorrowCapacity | string | Maximum borrow capacity in USDT |
accountLimits.maxOrdersPerMarket | integer | Maximum open orders allowed per market |
accountLimits.maxSubAccounts | integer | Maximum number of subaccounts allowed for the wallet |
accountLimits.maxTotalOrders | integer | Maximum total open orders across all markets |
Error Response
{
"id": "create-subaccount-1",
"status": 400,
"result": null,
"error": {
"code": 400,
"message": "Subaccount limit reached"
}
}Implementation Example
import { ethers } from 'ethers';
async function createSubaccount(ws, signer, masterSubAccountId, name = "") {
const nonce = Date.now();
const domain = {
name: "Synthetix",
version: "1",
chainId: 1,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
const types = {
CreateSubaccount: [
{ name: "masterSubAccountId", type: "uint256" },
{ name: "name", type: "string" },
{ name: "nonce", type: "uint256" },
{ name: "expiresAfter", type: "uint256" }
]
};
const expiresAfter = 0; // Set to a future timestamp to enforce expiry, or 0 for no expiry
const message = {
masterSubAccountId: BigInt(masterSubAccountId),
name,
nonce: BigInt(nonce),
expiresAfter: BigInt(expiresAfter)
};
const sig = await signer.signTypedData(domain, types, message);
const signature = ethers.Signature.from(sig);
ws.send(JSON.stringify({
id: `create-subaccount-${Date.now()}`,
method: "post",
params: {
action: "createSubaccount",
name,
nonce,
expiresAfter,
signature: { v: signature.v, r: signature.r, s: signature.s }
}
}));
}
// Usage: Create a new trading subaccount after authenticating the WebSocket as the owner
await createSubaccount(ws, signer, "1867542890123456789", "Grid Trading Bot");Code Examples
Create Basic Subaccount
{
"id": "create-basic",
"method": "post",
"params": {
"action": "createSubaccount",
"name": "Main Trading",
"nonce": 1735689600000,
"expiresAfter": 1735689900,
"signature": { "v": 28, "r": "0x...", "s": "0x..." }
}
}Create Strategy-Specific Subaccount
{
"id": "create-strategy",
"method": "post",
"params": {
"action": "createSubaccount",
"name": "Grid Trading Bot",
"nonce": 1735689600001,
"expiresAfter": 1735689901,
"signature": { "v": 28, "r": "0x...", "s": "0x..." }
}
}Create Subaccount Without Name
{
"id": "create-unnamed",
"method": "post",
"params": {
"action": "createSubaccount",
"nonce": 1735689600002,
"expiresAfter": 1735689902,
"signature": { "v": 28, "r": "0x...", "s": "0x..." }
}
}Implementation Notes
- Ownership Proof: Authenticate the WebSocket as an owned subaccount, then sign that authenticated subaccount as
masterSubAccountIdin the EIP-712 payload - Immediate Availability: Subaccount is immediately available for trading after creation
- Account Limits: Platform may enforce maximum number of subaccounts per wallet
- Initial State: New subaccounts have zero balances and no positions
- Security Inheritance: Each subaccount inherits master wallet security settings
Common Errors
| Error Code | Message | Description |
|---|---|---|
| 400 | Subaccount limit reached | Maximum subaccounts for this wallet exceeded |
| 400 | Invalid subaccount name | Name exceeds 50 characters or contains invalid characters |
| 401 | Authentication failed | Invalid signature |
| Error Code | Description | Retryable |
|---|---|---|
UNAUTHORIZED | EIP-712 signature validation failed | No |
VALIDATION_ERROR | Request validation failed | No |
MISSING_REQUIRED_FIELD | Required field is missing | No |
INVALID_FORMAT | Field format is invalid | No |
INVALID_VALUE | Invalid parameter value | No |
RATE_LIMIT_EXCEEDED | Too many requests in time window | Yes |
INSUFFICIENT_MARGIN | Not enough margin for trade | No |
ORDER_NOT_FOUND | Order does not exist | No |
OPERATION_TIMEOUT | Operation timed out | Yes |
Next Steps
- Get Subaccount - Retrieve account details
- Add Delegated Signer - Delegate trading access
- Place Orders - Start trading
- REST Alternative - REST API comparison
