Skip to main content
POST
/
v1
/
placeOrder
Place order
curl --request POST \
  --url https://api.testnet.arcus.xyz/v1/placeOrder \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: <api-key>' \
  --header 'X-Signature: <api-key>' \
  --header 'X-Timestamp: <api-key>' \
  --data '
{
  "address": "<string>",
  "marketId": 32767,
  "accountIndex": 4,
  "quantity": "<string>",
  "price": "<string>",
  "timestamp": 4611686018427388000,
  "goodTilTime": "<string>",
  "minSize": "<string>",
  "stopPrice": "<string>",
  "isPositionTPSL": false,
  "parentOrderId": "<string>",
  "clientId": "<string>",
  "clientTime": "<string>",
  "fillMode": "<string>",
  "reduceOnly": false,
  "signature": "<string>"
}
'
{
  "address": "<string>",
  "accountIndex": 4,
  "orderId": "<string>",
  "marketId": 32767,
  "marketDisplayName": "<string>",
  "quantity": "<string>",
  "price": "<string>",
  "createdAt": 123,
  "clientId": "<string>",
  "goodTilTime": "<string>",
  "reduceOnly": true,
  "updatedAt": 123,
  "remainingSize": "<string>",
  "filledSize": "<string>",
  "error": "<string>",
  "rateLimit": {
    "remaining": 123
  }
}
Place a single order. Requires address query parameter matching the master Ethereum address for X-API-Key (same as POST /createApiKey response).

Response behavior

Asynchronous. The endpoint returns either 202 Accepted (the common case — the signed request was forwarded to the matching engine and the body does not carry terminal state) or 200 OK (the gateway already had definitive state for the order by the time it responded; status reflects that state). In both cases the body echoes the accepted orderId / clientId so subscribers can correlate WebSocket events with the request. To observe the full order lifecycle (OPEN, FILLED, CANCELED, REJECTED, fills, rejection reasons, etc.) clients must subscribe to the orders WebSocket channel (and userFills for trade-level events).

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).

X-Signature
string
header
required

Lowercase hex-encoded Ed25519 signature (128 chars).

Single-order endpoints (placeOrder, cancelOrder, modifyOrder, and other non-batch mutating routes) sign over the ordersign typed canonical payload — a compact, key-sorted JSON object built from parsed request fields using engine-native integer values:

placeOrder:   {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,"g":N,"m":N,"op":1,"p":N,"q":N,"r":0|1,"s":N,"t":N,"v":1}
cancelOrder: {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,[,"id":"…"],"m":N,"op":2,"v":1}
modifyOrder: {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,[,"id":"…"],"m":N,"op":3,"p":N,"q":N,"v":1}

ct must equal the X-Timestamp header value. Keys in brackets are conditional (omitted when empty). op values: 1=place, 2=cancel, 3=modify. See the ordersign package for field definitions and reference signing code.

Other signed routes (e.g. createApiKey, tokens, userPreferences) still use the legacy scheme: signing_message = X-Timestamp + ACTION + canonicalJSON(body), where ACTION is the camelCase final path segment.

Batch endpoints (batchPlaceOrders, batchCancelOrders) do NOT use this header. They authenticate with per-element typed ordersign signatures embedded in the request body (see the global auth description and the per-field signature descriptions on OrderRequest / CancelOrderRequest).

Read endpoints are authenticated by ?address= (and optionally X-API-Key) only — no signature is required. canonicalJSON(body) is the JSON body with object keys sorted lexicographically at every level and no whitespace; the server canonicalizes the received body before verifying, so only the bytes signed over must be canonical. Required on all mutating / credential-creating 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
address
string
required

Master Ethereum address for this API key (must match POST /createApiKey for the same key). May be sent here and/or as the address query parameter; if both are set, they must match.

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

Perpetual market identifier (uint16). Map to display name via GET /markets. Used for orders, positions, funding, and market metadata.

Required range: 0 <= x <= 65535
accountIndex
integer
required

Account index (account index, 0–9). Identifies the account for orders, positions, fills, and API keys.

Required range: 0 <= x <= 9
orderSide
enum<string>
required
Available options:
BUY,
SELL
orderType
enum<string>
required

Execution style of the order. Use tpslType to mark an order as stop-loss or take-profit.

  • LIMIT — rests at the specified price until matched or expired.
  • MARKET — executes immediately at best available price. A price field is required and serves as a protective slippage bound. The price must be a positive decimal within 10% of the current mark price; deviations beyond that are rejected with MarketPriceSlippageToleranceTooHigh. TPSL MARKET legs must also supply a price, but it is validated against the stopPrice (trigger price) rather than the live mark price — the price must be within 10% of stopPrice, since at trigger time the mark price ≈ stopPrice.
Available options:
LIMIT,
MARKET
quantity
string
required

Order size in human-readable base-asset units (e.g. "0.1" for 0.1 BTC). Must be a positive value divisible by the market's step size (see GET /markets). Set to "0" for positionTpsl grouping — the engine resizes to the full open position at trigger time.

Minimum string length: 1
Pattern: ^(0|0\.[0-9]*[1-9][0-9]*|[1-9][0-9]*\.?[0-9]*)$
Example:

"0.1"

price
string
required

Order price in human-readable USD (e.g. "50000.5"). Must be a positive decimal divisible by the market's tick size (see GET /markets).

  • LIMIT — the resting limit price.
  • MARKET — required protective slippage bound (see orderType). Must be within 10% of the current mark price; deviations beyond that are rejected with MarketPriceSlippageToleranceTooHigh.
  • TPSL MARKET — also required. Must be within 10% of stopPrice (trigger price), since at trigger time the mark price ≈ stopPrice.
Minimum string length: 1
Pattern: ^(0|0\.[0-9]*[1-9][0-9]*|[1-9][0-9]*\.?[0-9]*)$
Example:

"50000.5"

timeInForce
enum<string>
required

GTT = Good Till Time (rests until goodTilTime), IOC = Immediate or Cancel, FOK = Fill or Kill, ALO = Add Liquidity Only (post-only)

Available options:
GTT,
IOC,
FOK,
ALO
timestamp
integer<int64>
required

Signature timestamp (epoch ms). Must be > 0.

Required range: 1 <= x <= 9223372036854776000
goodTilTime
string

Good-till-time (GTT): the order's expiration as an epoch-MICROSECOND timestamp (string) — the API's user-facing timestamp resolution, matching createdAt/updatedAt. REQUIRED for resting time-in-forces (GTT, ALO) and must be at least one month ahead of the current system timestamp; a nearer value is rejected. It is ignored for IOC and FOK, which never rest. The engine cancels the order (cancel reason Expired) the first time it would fill at or after this instant.

Pattern: ^[0-9]+$
Example:

"4102444800000000"

minSize
string | null

Minimum fill size in human-readable base-asset units.

stopPrice
string

Trigger price in human-readable USD. Required when tpslType is set; otherwise omit this field.

Minimum string length: 1
tpslType
enum<string>

Set to STOP_LOSS or TAKE_PROFIT to mark this as a trigger order. Requires stopPrice; for LIMIT execution, price is also required.

Available options:
STOP_LOSS,
TAKE_PROFIT
isPositionTPSL
boolean | null
default:false

When true, mark this TPSL as a position-level close: at trigger time the engine resizes the order to the user's full open position in marketId (and forces reduceOnly). If the position is zero, the TPSL is canceled instead of executed. Requires tpslType. At most one position-level TPSL of each trigger class (TP, SL) may be active per account+market — a second placement is rejected with POSITION_TPSL_ALREADY_EXISTS. Defaults to false (sized leg / partialTpsl behavior).

parentOrderId
string | null

Entry order id this TPSL leg is bound to (entryTpsl bundles). Set on each child TPSL placed alongside an entry order so the engine cascade-cancels every leg pointing at the entry when the entry is canceled. Requires tpslType. Omit for standalone TPSLs (partialTpsl, positionTpsl) and for non-TPSL orders.

clientId
string | null

Optional client-assigned identifier for this order.

Charset — only ASCII letters, digits, hyphens, and underscores ([A-Za-z0-9_-]) are accepted. This restricted set ensures JSON serialization is byte-identical across all SDK languages (Go, TypeScript, Python), which is required for cross-language signature compatibility. Strings containing any other character (spaces, @, Unicode, etc.) are rejected with HTTP 400 InvalidRequest.

Length — 1–36 characters.

Uniqueness — must be unique among the account's currently live orders. Reusing a clientId while the prior order is still open returns DUPLICATE_CLIENT_ID. The id is freed once the prior order reaches a terminal state (FILLED, CANCELED, REJECTED). An account may hold at most 10,000 live clientIds (TOO_MANY_CLIENT_IDS).

When set, the order can later be canceled or modified by clientId instead of orderId.

Maximum string length: 36
Pattern: ^[A-Za-z0-9_-]+$
clientTime
string | null

Client-side timestamp (epoch ms as string).

fillMode
string | null

Fill mode.

reduceOnly
boolean | null
default:false

If true, the order can only reduce an existing position. When reduceOnly is true, timeInForce must be IOC or FOK.

signature
string

Per-order Ed25519 signature (128 hex). REQUIRED when this order is an element of a POST /batchPlaceOrders orders array; omitted for single POST /placeOrder (which uses X-Signature in the HTTP header instead).

Covers the ordersign typed canonical payload for this order:

  • Plain orders (no tpsl_type): op=1 {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,"g":N,"m":N,"op":1,"p":N,"q":N,"r":0|1,"s":N,"t":N,"v":1}
  • TPSL / conditional orders (with tpsl_type): op=4 (OpPlaceUntriggered) Same field set as op=1 but "op":4. Using a distinct op prevents cross-replay between TPSL and plain placeOrder signatures.

In both cases ct must equal the shared X-Timestamp header value.

Response

Order processed and the gateway already has definitive state for it. status reflects that state (OPEN / FILLED / CANCELED / REJECTED). Treat this as best-effort enrichment of the 202 path; the orders WebSocket channel is still the source of truth for the full lifecycle.

Place-order response. The HTTP call is asynchronous and may return either:

  • 202 Accepted — common case. status is ACK and the body carries no terminal state; the orders WebSocket channel delivers the order's lifecycle.
  • 200 OK — the gateway already had definitive state for the order by the time it responded. status reflects that state (OPEN / FILLED / CANCELED / REJECTED) and enrichment fields (updatedAt, remainingSize, filledSize, rejectionReason) may be populated. Clients should treat this as best-effort and not rely on it.

In both cases the body echoes the accepted orderId and clientId for correlation. To observe the full lifecycle (fills, rejection reasons, etc.) clients must subscribe to the orders WebSocket channel (and userFills for trade-level events).

address
string
required

Master Ethereum address of the account that placed the order.

Example:

"0x1234567890abcdef1234567890abcdef12345678"

accountIndex
integer
required

Account index (subaccount) that placed the order.

Required range: 0 <= x <= 9
orderId
string
required

Server-generated order ID (hex string).

Example:

"a1b2c3d4e5f67890"

marketId
integer
required

Perpetual market identifier (uint16). Map to display name via GET /markets. Used for orders, positions, funding, and market metadata.

Required range: 0 <= x <= 65535
marketDisplayName
string
required

Market symbol (e.g. BTC-USD).

Example:

"BTC-USD"

side
enum<string>
required

Order side.

Available options:
BUY,
SELL
quantity
string
required

Order size in human-readable base-asset units.

price
string
required

Order price in human-readable USD.

status
enum<string>
required

ACK on the 202 path. On the 200 path, the definitive go-core state (OPEN / FILLED / CANCELED / REJECTED). Subscribe to the orders WebSocket channel for the full lifecycle.

Available options:
PENDING,
OPEN,
PARTIALLY_FILLED,
FILLED,
CANCELED,
MARGIN_CANCELED,
REJECTED,
UNTRIGGERED,
TPSL_PLACED,
TPSL_TRIGGERED,
TPSL_CANCELED,
LIQUIDATED,
ADL,
ACK,
CANCEL_ACKNOWLEDGED,
CANCEL_ALL_ACKNOWLEDGED,
CANCEL_PENDING,
ERROR
Example:

"ACK"

createdAt
integer<int64>
required

Creation timestamp (epoch microseconds).

clientId
string

Echo of the clientId, if provided.

type
enum<string>

Order type (e.g. LIMIT, MARKET).

Available options:
LIMIT,
MARKET
timeInForce
enum<string>

Echo of the time-in-force from the request.

Available options:
GTT,
IOC,
FOK,
ALO
goodTilTime
string

Expiration timestamp in epoch microseconds (as string), echoed from the request. Present for GTD/ALO orders that carry a good-till-time expiry; absent for GTC/IOC/FOK orders (which never expire). Matches the goodTilTime field on the Order shape.

reduceOnly
boolean

Echo of the reduce-only flag from the request.

updatedAt
integer<int64>

Epoch microseconds of the definitive go-core state when the gateway returned it on the 200 path. Omitted on the 202 path — definitive timestamps are delivered via the orders WebSocket channel.

remainingSize
string

Unfilled size at updatedAt, in human-readable base-asset units. Populated only on the 200 path; otherwise see the orders WebSocket channel.

filledSize
string

Cumulative filled size at updatedAt, in human-readable base-asset units. Populated only on the 200 path; otherwise see the orders WebSocket channel.

rejectionReason
enum<string>

Machine-readable reason when status is REJECTED on the 200 path. Otherwise rejection reasons are delivered via the orders WebSocket channel.

Available options:
POST_ONLY_WOULD_CROSS,
SELF_TRADE,
UNDERCOLLATERALIZED,
COULD_NOT_FILL,
IOC_CANCELED,
FOK_FAILED,
REDUCE_ONLY_WOULD_INCREASE,
TOO_MANY_CLIENT_IDS,
DUPLICATE_CLIENT_ID,
POSITION_TPSL_ALREADY_EXISTS,
ENTRY_TPSL_CANNOT_BE_POSITION_TPSL,
ORDER_WILL_TAKE_LIQUIDITY_DURING_MARKET_HALT,
ORDER_NOT_FOUND_FOR_MODIFY,
MODIFY_CHANGED_IMMUTABLE_FIELD,
MODIFY_ZERO_SIZE,
PRICE_WILL_EXCEED_MAXIMUM_OUTSIDE_RTH_TRADING_BOUND,
MODIFY_WOULD_CROSS_OUTSIDE_RTH_TRADING_BOUNDARY
error
string

Present when status is ERROR (e.g. batch item validation failure).

rateLimit
object

Per-subaccount order-pool rate-limit snapshot after this placement. Omitted when rate limiting is not configured.