Skip to main content
POST
/
v1
/
batchPlaceOrders
curl --request POST \
  --url https://api.testnet.arcus.xyz/v1/batchPlaceOrders \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: <api-key>' \
  --header 'X-Timestamp: <api-key>' \
  --data '
{
  "orders": [
    {
      "address": "0xAbCdEf1234567890AbCdEf1234567890AbCdEf12",
      "marketId": 1,
      "accountIndex": 0,
      "orderSide": "BUY",
      "orderType": "LIMIT",
      "quantity": "0.1",
      "price": "60000.0",
      "timeInForce": "GTT",
      "goodTilTime": "4102444800000000",
      "timestamp": 1713825891591
    },
    {
      "address": "0xAbCdEf1234567890AbCdEf1234567890AbCdEf12",
      "marketId": 1,
      "accountIndex": 0,
      "orderSide": "SELL",
      "orderType": "LIMIT",
      "quantity": "0.05",
      "price": "65000.0",
      "timeInForce": "GTT",
      "goodTilTime": "4102444800000000",
      "timestamp": 1713825891592
    }
  ]
}
'
{
  "responses": [
    {
      "orderId": "<string>",
      "clientTime": 123,
      "marketId": 32767,
      "marketDisplayName": "<string>",
      "updatedAt": 123,
      "remainingSize": "<string>",
      "filledSize": "<string>",
      "error": "<string>",
      "goodTilTime": "<string>"
    }
  ],
  "rateLimit": {
    "remaining": 123
  }
}
Place up to 100 orders in a single request. Requires address query parameter matching the master Ethereum address for X-API-Key (same as single POST /placeOrder). Signed per order. This endpoint does NOT use the X-Signature header. Each element of orders carries its own signature over the ordersign typed canonical payload for that order (op 1) — the same bytes a standalone placeOrder signs; the shared X-API-Key + X-Timestamp headers authenticate the batch. The whole batch consumes a single replay slot. The top-level grouping is not individually signed.

Response behavior

Asynchronous. The endpoint returns either 202 Accepted (the common case — the batch was forwarded to the matching engine and per-order rows do not carry terminal state) or 200 OK (the gateway already had definitive state for the batch by the time it responded; per-order status reflects that state). In both cases each row echoes the accepted orderId / clientId so subscribers can correlate WebSocket events with the request. To observe each order’s full lifecycle (OPEN, FILLED, CANCELED, REJECTED, fills, etc.) clients must subscribe to the orders WebSocket channel (and userFills for trade-level events). Per-order validation failures are still returned synchronously as status: ERROR with an error message in the corresponding responses[] entry; valid rows are forwarded to the matching engine (partial success).

TPSL groupings

Set the top-level grouping field to submit a take-profit / stop-loss bundle instead of plain orders. The three grouping modes are:

partialTpsl

Place 1–2 TPSL legs sized to a user-specified quantity. Every order must set tpslType (STOP_LOSS or TAKE_PROFIT) and stopPrice. Two legs must be one of each trigger type.
{
  "grouping": "partialTpsl",
  "orders": [
    { "tpslType": "TAKE_PROFIT", "stopPrice": "70000.0", "reduceOnly": true, ... },
    { "tpslType": "STOP_LOSS",   "stopPrice": "55000.0", "reduceOnly": true, ... }
  ]
}

positionTpsl

Same shape as partialTpsl but the engine resizes each leg to close the account’s full open position at trigger time (isPositionTPSL is implicitly true). Use quantity: "0" on each leg; the engine replaces it at trigger. At most one TP and one SL may be active per account+market — a second placement is rejected with POSITION_TPSL_ALREADY_EXISTS.
{
  "grouping": "positionTpsl",
  "orders": [
    { "tpslType": "TAKE_PROFIT", "stopPrice": "70000.0", "quantity": "0", "reduceOnly": true, ... },
    { "tpslType": "STOP_LOSS",   "stopPrice": "55000.0", "quantity": "0", "reduceOnly": true, ... }
  ]
}

entryTpsl

Submit an entry order together with 1–2 bound TPSL children in a single atomic bundle (2–3 orders total). The entry leg has no tpslType; each child leg sets tpslType. When the entry is canceled, all child TPSL legs are automatically cascade-canceled.
{
  "grouping": "entryTpsl",
  "orders": [
    { "orderType": "LIMIT", "price": "60000.0", "quantity": "0.1", ... },
    { "tpslType": "TAKE_PROFIT", "stopPrice": "70000.0", "quantity": "0.1", "reduceOnly": true, ... },
    { "tpslType": "STOP_LOSS",   "stopPrice": "55000.0", "quantity": "0.1", "reduceOnly": true, ... }
  ]
}

SBE wire encoding

Plain batches (grouping: na) are encoded as PlaceOrder messages (template 25), one per order. TPSL batches are encoded as a single PlaceTPSL message (template 26) carrying the full bundle.

Authorizations

X-API-Key
string
header
required

Hex-encoded Ed25519 public key (64 chars). The public key IS the API key — register it via POST /createApiKey. Required on every authenticated request, both read-only and signed.

X-Timestamp
string
header
required

Unix time in nanoseconds as a decimal string (e.g. "1713825891591000000"). Millisecond or second epochs are rejected with 401 Unauthorized. Must be within ±30,000 ms (MaxTimestampDriftMs, the drift window stays configured in milliseconds) of server wall-clock, or the request is rejected with 401 Unauthorized. Required on all mutating / credential-creating endpoints. This same value must appear as the ct field in the ordersign typed canonical payload (single-order endpoints) or in each element's ct field (batch endpoints).

Query Parameters

address
string
required

Master Ethereum address for this API key (must match address from POST /createApiKey for the same key). Required on REST for account-scoped reads and for place/cancel. Invalid hex → 400; mismatch with key → 403.

20-byte EVM address as hex: optional 0x or 0X prefix and exactly 40 hexadecimal digits. API responses normalize to lowercase af after 0x.

Pattern: ^(0x|0X)?[0-9a-fA-F]{40}$

Body

application/json

Request body for POST /batchPlaceOrders. Supports plain limit/market orders as well as three TPSL grouping modes controlled by the top-level grouping field.

Plain batch (grouping omitted or "na") Send up to 100 standard LIMIT or MARKET orders.

TPSL groupings — set grouping to one of partialTpsl, positionTpsl, or entryTpsl to submit a trigger-order bundle. Shape constraints are enforced at the request level:

  • partialTpsl — 1 or 2 TPSL legs; every order must set tpslType. Two legs must be one STOP_LOSS and one TAKE_PROFIT. The order size is user-controlled.
  • positionTpsl — same shape as partialTpsl but the engine resizes each leg to the full open position at trigger time. isPositionTPSL is implicitly true.
  • entryTpsl — 2 or 3 orders: exactly one entry leg (no tpslType) plus 1–2 TPSL child legs. The TPSL children are automatically cascade-canceled when the entry is canceled.
orders
object[]
required

Array of orders to place. Length constraints depend on grouping:

  • na (default): 1–100 orders.
  • partialTpsl / positionTpsl: 1–2 TPSL orders.
  • entryTpsl: 2–3 orders (1 entry + 1–2 TPSL children).
Required array length: 1 - 100 elements
grouping
enum<string>

Classifies a batchPlaceOrders submission so the gateway can route and validate it correctly. Omit or set to "na" for a plain (non-TPSL) batch.

ValueDescription
naDefault. Plain limit/market orders only; no TPSL routing.
partialTpsl1–2 TPSL legs sized to a specific quantity (user-chosen partial position close). Every order must set tpslType.
positionTpsl1–2 TPSL legs that close the full open position at trigger time (isPositionTPSL forced true). Every order must set tpslType. At most one TP and one SL per account+market.
entryTpsl2–3 orders: exactly one entry order (no tpslType) plus 1–2 bound TPSL child legs (tpslType set). The TPSL children are cascade-canceled if the entry is canceled.
Available options:
na,
partialTpsl,
positionTpsl,
entryTpsl

Response

Batch processed and the gateway already has definitive state for the rows. Per-order status reflects that state. Treat as best-effort enrichment of the 202 path; the orders WebSocket channel is still the source of truth.

responses
object[]
required
rateLimit
object

Per-subaccount order-pool rate-limit snapshot after charging the whole batch (one snapshot for the request, not per row). Omitted when rate limiting is not configured.