Place Orders
Place one or more orders on Synthetix's orderbook. This endpoint always accepts an array of orders for consistency, even when placing a single order.
Endpoint
POST https://papi.synthetix.io/v1/tradeRequest
Request Format
{
"params": {
"action": "placeOrders",
"subAccountId": "1867542890123456789",
"orders": [
{
"symbol": "BTC-USDT",
"side": "buy",
"orderType": "limitGtc",
"price": "50000.00",
"triggerPrice": "",
"quantity": "0.1",
"reduceOnly": false,
"postOnly": false,
"isTriggerMarket": false,
"closePosition": false,
"clientOrderId": "0x1234567890abcdef1234567890abcdef"
}
],
"grouping": "na",
"source": "direct"
},
"nonce": 1704067200000,
"signature": {
"v": 28,
"r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
},
"expiresAfter": 1704067300
}Request Parameters
Action Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
params | object | Yes | Parameters object containing method details |
params.action | string | Yes | Must be "placeOrders" |
params.subAccountId | string | Yes | Subaccount identifier |
params.orders | array | Yes | Array of order objects (minimum 1 order) |
params.grouping | string | No | Order grouping: "na", "normalTpsl", "positionTpsl", "twap" |
params.source | string | No | Request source identifier for tracking and rebates (max 100 characters). Use "direct" for generic integrations |
Common Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
nonce | integer | Yes | Positive integer, incrementing nonce |
signature | object | Yes | EIP-712 signature object |
expiresAfter | integer | No | Expiration timestamp (5x rate limit on stale cancels) |
Order Object
| Parameter | Type | Required | Description |
|---|---|---|---|
symbol | string | Yes | Market symbol (e.g., "BTC-USDT", "ETH-USDT") |
side | string | Yes | "buy" or "sell" |
orderType | string | Yes | One of: limitGtc, limitIoc, limitAlo, limitGtd, market, triggerSl, triggerTp, twap |
price | string | Yes | Price as string. Use empty string when not applicable (market orders and trigger market) |
quantity | string | Yes | Order size as string |
reduceOnly | boolean | Yes | Only reduce existing position |
postOnly | boolean | Conditional | Whether order must be maker (no immediate match). Only used for limitGtc and limitGtd orders. Must be false for all other order types |
triggerPrice | string | Yes | Trigger price as string. Required for triggerSl and triggerTp; empty string otherwise |
isTriggerMarket | boolean | Yes | Execution type for trigger orders. true for market-on-trigger, false for limit-on-trigger. Must be false for non-trigger types |
closePosition | boolean | Yes | Close entire position when triggered (TP/SL orders only). Must be false for non-trigger types |
clientOrderId | string | No | 128-bit hex string (0x + 32 hex chars) |
triggerPriceType | string | Conditional | Price type used for trigger evaluation: "mark" or "last". Defaults to "mark" when omitted. Applies to triggerSl and triggerTp orders only |
expiresAt | integer | Conditional | Expiration timestamp in Unix seconds. Required for limitGtd orders; must be 10 seconds to 24 hours in the future. Not valid for other order types |
durationSeconds | integer | Conditional | Total TWAP execution window in seconds. Required for twap orders only. Must be between 300 (5 minutes) and 86400 (24 hours) |
intervalSeconds | integer | No | Interval between TWAP child chunks in seconds. Applies to twap orders only. Must be one of 10, 30, 60, 120, 180, 300, 600. Omit or set to 0 to use the default of 30 |
intervalVarianceBps | integer | No | Randomization of child order timing intervals. Applies to twap orders only. Multiples of 100 bps, capped at 2000 bps (20%). Default 0 (no variance) |
sizeVarianceBps | integer | No | Randomization of child order sizes. Applies to twap orders only. Multiples of 100 bps, capped at 2000 bps (20%). Default 0 (no variance) |
Note on postOnly: The postOnly field is a request parameter but is not included in the EIP-712 signed Order type. It is processed server-side after signature verification. The EIP-712 Order type contains: symbol, side, quantity, orderType, price, triggerPrice, reduceOnly, isTriggerMarket, clientOrderId, closePosition.
Order Type Enum and Rules
limitGtc: price required;isTriggerMarket=false;triggerPrice=""limitIoc: price required;isTriggerMarket=false;triggerPrice=""limitAlo: price required;isTriggerMarket=false;triggerPrice=""limitGtd: price required;isTriggerMarket=false;triggerPrice="";expiresAtrequired (10s–24h in the future);postOnlyacceptedmarket: price="";isTriggerMarket=false;triggerPrice=""triggerSl:triggerPricerequired;isTriggerMarketdetermines execution; iftruethenprice="", iffalsethenpricerequired; optionaltriggerPriceType("mark"or"last", defaults to"mark")triggerTp: same rules astriggerSltwap: Time-Weighted Average Price order.triggerPrice="";expiresAtmust be absent;durationSecondsrequired (300–86400); optionalintervalSecondsfrom the allowed set (10,30,60,120,180,300,600, default30); optionalpricesets a limit price, omit for no limit; exactly one order per request; usegrouping: "twap". See TWAP Orders for full details.
Examples
Limit GTC
{
"orderType": "limitGtc",
"price": "50000.00",
"triggerPrice": "",
"isTriggerMarket": false,
"postOnly": false,
"closePosition": false
}Limit GTC (Post-Only)
{
"orderType": "limitGtc",
"price": "50000.00",
"triggerPrice": "",
"isTriggerMarket": false,
"postOnly": true,
"closePosition": false
}Limit GTD (Good Till Date)
{
"orderType": "limitGtd",
"price": "50000.00",
"triggerPrice": "",
"isTriggerMarket": false,
"postOnly": false,
"closePosition": false,
"expiresAt": 1704070800
}Market
{
"orderType": "market",
"price": "",
"triggerPrice": "",
"isTriggerMarket": false,
"closePosition": false
}Trigger SL (Market)
{
"orderType": "triggerSl",
"price": "",
"triggerPrice": "50000",
"isTriggerMarket": true,
"closePosition": true
}Trigger TP (Limit)
{
"orderType": "triggerTp",
"price": "49950.00",
"triggerPrice": "50000.00",
"isTriggerMarket": false,
"closePosition": true
}TWAP
{
"orderType": "twap",
"symbol": "BTC-USDT",
"side": "buy",
"quantity": "1.5",
"price": "",
"triggerPrice": "",
"isTriggerMarket": false,
"reduceOnly": false,
"closePosition": false,
"durationSeconds": 3600,
"intervalSeconds": 60
}Time in Force (TIF) Options
| Value | Name | Description |
|---|---|---|
gtc | Good Till Canceled | Order remains active until filled or canceled |
gtd | Good Till Date | Order active until filled, canceled, or expiresAt timestamp reached |
ioc | Immediate or Cancel | Fill immediately available quantity, cancel remainder |
postOnly | Post-Only | Set postOnly: true on limitGtc orders to ensure maker-only execution |
Response
Response Structure
| Field | Type | Description |
|---|---|---|
status | string | "ok" for processed requests or "error" for request-level failures |
response | object | Contains operation results (omitted when status is "error") |
response.statuses | array | Array of status objects, one per order submitted (check each for success/error) |
error | object | Error details (only present when status is "error") |
requestId | string | Request tracking identifier |
traceId | string | Trace ID for debugging |
timestamp | integer | Unix milliseconds timestamp |
Order Status Types
Each order in the request receives one status response:
| Status Type | Description | Fields |
|---|---|---|
resting | Order placed in orderbook | order (canonical), id (deprecated), expiresAt (optional, Unix ms, present for limitGtd orders) |
filled | Order completely filled | order (canonical), id (deprecated), avgPrice, totalSize, expiresAt (optional, Unix ms) |
canceled | Order was canceled | order (canonical), id (deprecated) |
error | Order rejected | error (message string), errorCode (machine-readable code) |
Success Response Examples
Single Order - Resting
{
"status": "ok",
"response": {
"statuses": [
{
"resting": {
"order": {
"venueId": "1948058938469519360",
"clientId": "cli-1948058938469519360"
},
"id": "1948058938469519360"
}
}
]
},
"requestId": "5ccf215d37e3ae6d"
}Single Order - Filled
{
"status": "ok",
"response": {
"statuses": [
{
"filled": {
"order": {
"venueId": "1948058938469519361",
"clientId": "cli-1948058938469519361"
},
"id": "1948058938469519361",
"avgPrice": "50001.25",
"totalSize": "0.1"
}
}
]
},
"requestId": "5ccf215d37e3ae6d"
}Single Order - Canceled
{
"status": "ok",
"response": {
"statuses": [
{
"canceled": {
"order": {
"venueId": "1948058938469519362",
"clientId": "cli-1948058938469519362"
},
"id": "1948058938469519362"
}
}
]
},
"requestId": "5ccf215d37e3ae6d"
}Multiple Orders - Mixed Results (Partial Success)
Batch requests can have mixed results where some orders succeed and others fail. The response still has status: "ok" because the request was processed successfully—check each item in the statuses array:
{
"status": "ok",
"response": {
"statuses": [
{
"resting": {
"order": {
"venueId": "1948058938469519360",
"clientId": "cli-1948058938469519360"
},
"id": "1948058938469519360"
}
},
{
"filled": {
"order": {
"venueId": "1948058938469519361",
"clientId": "cli-1948058938469519361"
},
"id": "1948058938469519361",
"avgPrice": "2999.50",
"totalSize": "1.0"
}
},
{
"error": "insufficient margin: additional needed 500.00, available 100.12",
"errorCode": "INSUFFICIENT_MARGIN",
"order": { "venueId": null, "clientId": "cli-1948058938469519362" }
}
]
},
"requestId": "5ccf215d37e3ae6d",
"traceId": "abc123def456",
"timestamp": 1704067200000
}See Batch Request Error Handling for details on handling partial success scenarios.
Migration Note: Use *.order.venueId as canonical in each status payload. Legacy id remains for compatibility.
Error Response
Request-level errors return an error status. For per-item errors in batch requests, see Batch Request Error Handling.
{
"status": "error",
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid signature",
"category": "AUTH",
"retryable": false
},
"requestId": "5ccf215d37e3ae6d",
"traceId": "abc123def456",
"timestamp": 1704067200000
}Examples
Place Single Limit Order (GTC)
{
"params": {
"action": "placeOrders",
"subAccountId": "1867542890123456789",
"orders": [
{
"symbol": "BTC-USDT",
"side": "buy",
"orderType": "limitGtc",
"price": "50000.00",
"triggerPrice": "",
"quantity": "0.1",
"reduceOnly": false,
"postOnly": false,
"isTriggerMarket": false,
"closePosition": false
}
]
},
"nonce": 1704067200000,
"signature": {
"v": 28,
"r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
}
}Place Single Limit GTD Order
{
"params": {
"action": "placeOrders",
"subAccountId": "1867542890123456789",
"orders": [
{
"symbol": "BTC-USDT",
"side": "buy",
"orderType": "limitGtd",
"price": "50000.00",
"triggerPrice": "",
"quantity": "0.1",
"reduceOnly": false,
"postOnly": false,
"isTriggerMarket": false,
"closePosition": false,
"expiresAt": 1704070800
}
]
},
"nonce": 1704067200000,
"signature": {
"v": 28,
"r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
}
}Place Multiple Orders (Batch) — Mixed types
{
"params": {
"action": "placeOrders",
"subAccountId": "1867542890123456789",
"orders": [
{
"symbol": "BTC-USDT",
"side": "buy",
"orderType": "limitGtc",
"price": "49000.00",
"triggerPrice": "",
"quantity": "0.05",
"reduceOnly": false,
"postOnly": false,
"isTriggerMarket": false,
"closePosition": false
},
{
"symbol": "BTC-USDT",
"side": "buy",
"orderType": "limitGtc",
"price": "48000.00",
"triggerPrice": "",
"quantity": "0.1",
"reduceOnly": false,
"postOnly": false,
"isTriggerMarket": false,
"closePosition": false
},
{
"symbol": "BTC-USDT",
"side": "sell",
"quantity": "0.15",
"reduceOnly": true,
"orderType": "triggerSl",
"price": "",
"triggerPrice": "45000",
"isTriggerMarket": true,
"closePosition": false
}
],
"grouping": "na"
},
"nonce": 1704067200000,
"signature": {
"v": 28,
"r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
}
}:::info Rate Limiting This batch contains 3 orders, so it consumes 12 tokens (3 × 4) from your per-subaccount bucket. See Rate Limits for details. :::
TWAP Orders
TWAP (Time-Weighted Average Price) orders split a large order into equal-sized child chunks that are submitted at fixed intervals over a chosen duration. Use TWAPs to reduce market impact when entering or exiting large positions.
A TWAP order is placed via the same placeOrders action with grouping: "twap" and orderType: "twap".
TWAP Request
{
"params": {
"action": "placeOrders",
"subAccountId": "1867542890123456789",
"grouping": "twap",
"orders": [
{
"symbol": "BTC-USDT",
"side": "buy",
"orderType": "twap",
"quantity": "1.5",
"price": "",
"triggerPrice": "",
"isTriggerMarket": false,
"reduceOnly": false,
"closePosition": false,
"durationSeconds": 3600,
"intervalSeconds": 60,
"sizeVarianceBps": 2000,
"intervalVarianceBps": 1000
}
]
},
"nonce": 1704067200000,
"signature": {
"v": 28,
"r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
},
"expiresAfter": 1704067300
}TWAP Order Fields
All standard order fields apply. The TWAP-specific fields are:
| Field | Type | Required | Description |
|---|---|---|---|
orderType | string | Yes | Must be "twap" |
durationSeconds | integer | Yes | Total execution window in seconds. Must be between 300 (5 minutes) and 86400 (24 hours) |
intervalSeconds | integer | No | Interval between chunks in seconds. Must be one of 10, 30, 60, 120, 180, 300, 600. Omit or set to 0 to use the default of 30 |
intervalVarianceBps | integer | No | Randomization of child order timing intervals. Multiples of 100 bps, capped at 2000 bps (20%). Default 0 (no variance) |
sizeVarianceBps | integer | No | Randomization of child order sizes. Multiples of 100 bps, capped at 2000 bps (20%). Default 0 (no variance) |
quantity | string | Yes | Total quantity across all chunks |
price | string | No | Optional limit price; chunks are skipped for any interval where the chunk would fill outside this limit. Omit (empty string) for no limit |
side | string | Yes | "buy" or "sell" |
symbol | string | Yes | Market symbol |
reduceOnly | boolean | No | Standard reduce-only flag |
clientOrderId | string | No | Standard client order ID |
Fields that must not be set on a TWAP order (validation fails otherwise):
triggerPrice— must be emptyexpiresAt— must be absent
Allowed intervalSeconds Values
10, 30, 60, 120, 180, 300, 600.
Any other value returns HTTP 400 with TWAP interval invalid, allowed=[10 30 60 120 180 300 600]. When intervalSeconds is 0 or omitted, the server defaults to 30.
TWAP Validation Rules
All rules are enforced server-side and return HTTP 400 on failure:
| Rule | Error message |
|---|---|
durationSeconds ≥ 300 | TWAP duration must be at least 5 minutes |
durationSeconds ≤ 86400 | TWAP duration exceeds maximum of 24 hours |
intervalSeconds in allowed set | TWAP interval invalid, allowed=[10 30 60 120 180 300 600] |
| Division produces ≥ 2 chunks | TWAP duration must produce at least two chunks |
quantity > 0 | TWAP total quantity must be greater than zero |
price limit non-negative if set | TWAP price limit must be positive |
| Total notional ≥ $10,000 USD | TWAP total order size must be at least $10,000 USD equivalent |
Exactly one order in orders array | HTTP 400 |
sizeVarianceBps / intervalVarianceBps multiples of 100 | HTTP 400 |
sizeVarianceBps / intervalVarianceBps ≤ 2000 | HTTP 400 |
The notional check uses the current mark price. If the mark price is temporarily unavailable, the request is rejected with mark price not available for TWAP min order size validation.
TWAP child orders are scheduled deterministically using the parent venueOrderId as a seed. When variance fields are set, child order sizes and/or timing intervals vary within the configured bounds.
TWAP Response
On success, the TWAP parent order is accepted and rests in the open orders table immediately. Child-chunk fills arrive asynchronously as the TWAP progresses through each interval. The parent's execution progress can be inspected via getOpenOrders (see the twapDetails object) while the TWAP is active, and via getOrdersHistory once it completes or is cancelled.
TWAP Signing
TWAP orders use the same PlaceOrders EIP-712 primary type as all other placeOrders requests — there is no separate TWAP action type or signing domain. In the signed payload, set grouping to "twap".
durationSeconds, intervalSeconds, sizeVarianceBps, and intervalVarianceBps are passed in the HTTP params layer and are not included in the EIP-712 signature. Only the base Order fields and the top-level grouping, subAccountId, nonce, and expiresAfter are signed.
Chunk Execution Behaviour
For reference when building monitoring or progress UIs:
- The TWAP is divided into
floor(durationSeconds / intervalSeconds)equal chunks - Each chunk's quantity is
totalQuantity / numChunks, truncated to market quantity precision. The final chunk absorbs any remainder from truncation - Chunks are submitted as market orders at each interval. If a
pricelimit is set, chunks that would fill outside the limit are skipped for that interval - If the minimum chunk size computes to zero (extreme precision edge case), the TWAP collapses to a single chunk covering the full quantity
- Progress is reported in real time via the
twapDetailsobject on Get Open Orders
Cancelling a TWAP
There is no separate cancel-TWAP endpoint. Use the existing cancelOrders action with the TWAP parent's venue order ID or client order ID, or use cancelAllOrders. Cancelling a TWAP parent automatically cancels any in-flight child chunk orders; the parent transitions to Cancelled status.
Implementation Notes
- Order nonces must be positive integers, incrementing and unique per request
- Client order IDs enable request tracking with 128-bit hex format (0x prefix + 32 hex characters)
- Limit orders require price specification, trigger orders require trigger price
- Batch operations process orders independently with individual status responses
- Order grouping types:
"na"(none),"normalTpsl"(stop-loss/take-profit),"positionTpsl"(position-based),"twap"(TWAP execution) - All price and quantity values must be provided as strings for precision preservation
Linked TP/SL sibling cancellation
When a take-profit or stop-loss order in a linked normalTpsl or multipleTpsl pair fires and publishes to matching, the platform automatically cancels the sibling order and creates an explicit Cancelled history entry for it:
- The filled leg (TP or SL) appears in
getOrderHistorywithstatus: "Filled"as expected. - The auto-cancelled sibling now appears in
getOrderHistorywithstatus: "Cancelled"and a populatedcancelledAttimestamp.
This applies to both intra-pair and cross-pair siblings within multipleTpsl groupings. Locally cancelled conditional orders (e.g., due to insufficient position size) continue to emit history only for the triggered order itself.
Error Handling
Common Errors
| Error Code | Description | Solution |
|---|---|---|
INSUFFICIENT_MARGIN | Account lacks required margin | Add collateral or reduce order size |
COLLATERAL_PRICE_STALE | Collateral price exceeds staleness threshold; order would increase exposure | Wait for fresh collateral prices or cancel the order attempt; do not retry immediately |
REQUEST_EXPIRED | expiresAfter timestamp has passed | Use longer expiration or remove |
INVALID_VALUE | Market symbol not recognized | Check supported markets |
QUANTITY_TOO_SMALL | Below minimum order size | Check market minimums |
POST_ONLY_WOULD_TRADE | Post-only order would take liquidity | Adjust price or use different TIF |
MAX_ORDERS_EXCEEDED | Maximum order limit reached | Cancel existing orders first |
REDUCE_ONLY_NO_POSITION | No position for reduce-only order | Remove reduce-only flag |
MARKET_CLOSED | Market is not open for trading | Wait for market to open |
Batch Order Errors
For batch orders, each order is validated and processed independently:
- Request-level errors (invalid signature, rate limit) fail the entire batch with an error response
- Per-item errors (insufficient margin, invalid price) return HTTP 200 with individual error statuses
- The
statusesarray preserves the order of your request—index 0 corresponds to your first order - Successfully processed orders are not rolled back if other orders fail
See Batch Request Error Handling for complete details and code examples.
Rate Limiting
- Each order in the batch consumes base cost - A request with 5 orders consumes 5 × 4 = 20 tokens from your per-subaccount bucket
- Bucket size: 1,000 tokens per 10 seconds per subaccount (see Rate Limits)
- Failed orders due to validation errors still count against rate limits
- See Rate Limits for complete details
Next Steps
- Authentication - Set up request signing
- WebSocket Trading - Real-time alternative
- Error Handling - Handle errors properly